Steel forwards a caller-supplied X-Forwarded-For and appends its own, so the last value is the only trustworthy one
proposal 12, opened by backlog
State accepted — agreed, and not yet done
No mark closes this yet, and nothing in the forge sets that field. The review lane is what will. A mark in Ore is a point in the log, so naming one here will name a state anybody can go and render rather than a sentence somebody wrote.
Steel's proxy path copies every caller header through to the upstream, skipping only `host`, `connection`, `content-length` and `transfer-encoding`, and then appends its own `X-Forwarded-For` with the real peer address (`~/usr/code/rust/fe2o3/fe2o3_steel/src/srv/https.rs:919-936`). A request that arrives carrying its own `X-Forwarded-For` therefore reaches the upstream with two values.
That matters because `HeaderFields::get_one` returns the first. An application behind Steel that reads the first value is reading a value the caller chose, which for a rate limiter means the caller chooses the bucket it is counted in -- which is no limit at all. The forge reads the last, which is Steel's own and cannot be spoofed. This was found in the same pass as a related defect on the forge's own side: the address guard keyed on the socket peer, and behind Steel every request arrives from loopback, which is whitelisted for Daimond's gateway, so deployment would have whitelisted the entire public web.
The application-side fix is in. The upstream fix is not, and it is the one that matters for every future caller, because the next application behind Steel will read the first value unless Steel stops passing it. The correct upstream behaviour is to strip a caller-supplied `X-Forwarded-For` before appending, and the same applies to `X-Forwarded-Proto`, which is appended unconditionally two lines later and is equally spoofable.
The lane is authorised. rc-1 holds a `fe2o3_steel` worktree with the owner's authorisation to make this fix, briefed not to commit and not to push, because fe2o3 is public and pushing is the owner's call.
What remains: the strip, a test that a spoofed header does not survive the proxy, and the owner's decision on the push.
Transcribed from the project record on 2026-08-14. The words quoted in the discussion below are their named authors' own; the `backlog` voice carried them here and wrote none of them.
1 reply
-
backlog
Ore session: Steel does not strip a caller-supplied X-Forwarded-For. It copies caller headers through, then appends its own -- so a spoofing request arrives with two values, and HeaderFields::get_one returns the first. Reading the first would let any caller choose the address it is limited by, which is no limit at all. The forge reads the last.