C.W.K.
Stream
Lesson 02 of 04 · published

The Socket Proves Local, Not the Header

~14 min · firelink, loopback, socket, dns-rebinding

Level 0Cold Ash
0 XP0/32 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"Anyone can claim to be localhost. Only the operating system can prove it."

The One Place a PIN Is Skipped

Firelink grants exactly one no-PIN path: a browser genuinely on the same machine. Sit at the office box, open a loopback URL, and you get a durable local write session without entering the family PIN — because you're demonstrably local. The whole question is how to prove 'demonstrably.' Get that wrong and the no-PIN path becomes a remote bypass; get it right and it's a safe convenience for the one person physically at the keyboard.

Trust the Socket, Not the Claim

The proof is the socket address the connection actually arrived on — a loopback socket (a 127.0.0.0/8 or IPv6 ::1 peer) to a loopback host. The socket is set by the operating system when the connection is made; a client cannot forge it. What a client can forge is the Host header — it can send any hostname it likes, including 'localhost.' So Firelink never trusts the requested host to establish local origin. The requested host can only narrow the decision (a loopback socket must also be talking to a loopback host), and it can never turn a remote socket into a local one. Trust flows from the OS-established socket; the client-supplied header can subtract, never add.

Why 'Narrow, Never Widen' Is the Whole Trick

This asymmetry is the safety property. A remote attacker can send a request with Host: localhost all day — and it will never be treated as local, because their socket is remote and the header can't widen that. Meanwhile a genuinely local browser is confirmed by its loopback socket. The direction of the rule is what matters: information the client controls (the header) is only ever allowed to make trust smaller; information the client can't control (the socket) is what's allowed to grant it. Any auth rule where a client-supplied value can increase trust is a rule with a bypass waiting inside it.

Let trust flow only from facts the client cannot forge, and let client-supplied values narrow that trust but never widen it. The socket is the OS's word; the Host header is the client's word. Establish local origin from the socket, allow the header to add a constraint, and a remote client can never talk its way into local privilege.

Code

Socket grants; header can only narrow·python
def is_local_session(conn) -> bool:
    # The SOCKET peer is set by the OS at connect time. Unforgeable.
    socket_is_loopback = conn.peer_ip in LOOPBACK_RANGES   # 127.0.0.0/8, ::1

    # The Host HEADER is chosen by the client. Forgeable. It may only NARROW.
    host_is_loopback = conn.requested_host in LOOPBACK_HOSTS  # localhost, 127.*, ::1

    # Local origin requires BOTH: a loopback socket AND a loopback host.
    return socket_is_loopback and host_is_loopback

# Remote attacker sends 'Host: localhost' over a remote socket:
#   socket_is_loopback = False  ->  is_local_session = False.  No bypass.
# The header narrowed nothing into existence; the socket had the final say.
#
# LAN / tailnet clients (remote socket) fall through to the PIN flow.
# There is no header, cookie, or claim that upgrades a remote socket to local.

External links

Exercise

Find an authorization decision in a system you know that reads a client-supplied value (a header, a query param, a field in the request body) to decide trust or identity. Ask: can a client set that value to increase its own privilege? If yes, you have a widen-from-client bug. Redesign it so trust comes from something the client can't forge (a verified session, the socket, a signed claim) and the client value can only add a restriction.
Hint
The tell is any 'if request says X, grant more.' Flip it: grant based on a server-established fact, and let the request only ask for less than that. A value the caller controls should never be the thing that opens a door.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.