fix(audit): trusted proxy hops is 2, measured rather than assumed #21
No reviewers
Labels
No labels
waiting-on-julian
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jlxq0/jmap-mcp!21
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "fix/trusted-proxy-hops-2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The deployed
v0.2.15writes the edge gateway's address into thelast_usedprovenance record as the client's, on every authenticated request. Refs #14.DEFAULT_TRUSTED_PROXY_HOPSwas1, commented"(Traefik)". There is no Traefik on this cluster.Two independent measurements, both 2
xff_entries=2oddie-apps/edge-config+ Cilium configclient -> Caddy edge -> Cilium gateway -> pod. The edge sets notrusted_proxies(0 occurrences in 80 site blocks), so it replaces XFF with its peer, giving entry 1, the client. Cilium runsgateway-api-xff-num-trusted-hops: 0, so Envoy appends the downstream address, giving entry 2, the edge.One is read at the pod, the other from the edge repository. They share no instrument.
The route is singular as well:
203.24.209.5times out on tcp/443 across three attempts while203.24.209.8answers 200 on all three, so every external client traverses the same chain.parse_client_ipindexes from the right, solen=2, hops=1 -> parts[1], the edge.hops=2 -> parts[0], the client.The claim I had wrong, found by Codex in review
I had said a higher hop count always fails safe, because
len < hops -> None. It does not protect against a client padding a short chain.Behind a front-most proxy that appends, a client sending
X-Forwarded-For: 1.2.3.4produces1.2.3.4, <client>.lenis 2, the guard never fires, andhops=2selects the attacker's value. Athops=1the same header yields what the proxy actually saw.So raising this number is not a free safety margin: behind an appending proxy it turns a wrong address into a chosen one.
2 is correct here only because our edge replaces, which is a property of the edge rather than of the number. The doc comment and
a_padded_chain_defeats_a_higher_hop_countnow say so, so the next person raising it meets the reason rather than the tidy version.Tests, all mutation-checked
13parse_client_ipindexes from the leftGates
Five on the pinned 1.93.0:
fmt,clippy --all-targets --all-features -D warnings,test(170),audit,deny.Also
Four
Traefikreferences removed, zero remain. The comment names the systems that produce the number, and says a deployment not behind this edge must override viaJMAP_MCP_TRUSTED_PROXY_HOPS— a backend behind the LAN-onlyhomegateway sees one entry.Cargo.tomlbumped to 0.2.16; this releases rather than taking a temporary env override, since the override would be a line somebody has to remove later.DEFAULT_TRUSTED_PROXY_HOPS was 1, commented "(Traefik)". There is no Traefik on this cluster, and parse_client_ip counts in from the right, so 1 against a two-entry header selected the edge's own address and wrote it into the last_used provenance record as the client's. Not blank, not obviously wrong: a well-formed address identifying the wrong party in the field read during an incident. Two independent measurements agree on 2: observed the pod logged xff_entries=2 on 7 authenticated requests, 2026-08-27T03:37:47Z, the only such requests it has served derived client -> Caddy edge -> Cilium gateway -> pod. The edge sets no trusted_proxies so it REPLACES XFF with its peer (entry 1, the client); Cilium runs gateway-api-xff-num-trusted-hops: 0 so Envoy APPENDS the downstream address (entry 2, the edge) One is read at the pod and the other from oddie-apps/edge-config, sharing no instrument. The route is singular too: 203.24.209.5 times out on 443 across three attempts while 203.24.209.8 answers 200 on all three. 2 is safe here ONLY because the edge replaces rather than appends, and Codex found that in review: "len < hops -> None" does not protect against a client PADDING a short chain. Behind a front-most proxy that appends, a client sending X-Forwarded-For: 1.2.3.4 produces "1.2.3.4, <client>", len is 2, the guard never fires, and hops=2 selects the attacker's value. So a higher hop count is not a free safety margin: behind an appending proxy it turns a wrong address into a chosen one. I had told Alan the opposite; the comment and a test now carry the correction rather than the tidy version. The doc comment names the systems that produce the number instead of a product that is not deployed, and says a deployment not behind that edge must override via JMAP_MCP_TRUSTED_PROXY_HOPS. A backend behind the LAN-only home gateway sees one entry. Tests pin the constant, the pairing that makes it matter, and the padding case. Mutation-checked: reverting the default to 1 goes red on the message naming the regression, 3 goes red too so it is not a tautology, and reversing the index direction turns the padding test red. Five gates green on 1.93.0: fmt, clippy -D warnings, test (170), audit, deny. Version bumped to 0.2.16 for the release. Refs #14 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>