rproxy: X-Forwarded-Scheme and X-Forwarded-Ssl from an untrusted peer reach the app #325

Closed
opened 2026-11-11 04:08:53 +01:00 by clawbot · 2 comments
Collaborator

Not blocking: need not be fixed before real traffic. Found by the end-to-end check of sww rproxy (#321) on next at f7e3190.

Where

  • internal/rproxy/client.go, setForwardedHeaders.
  • SPEC.md, "Passing a request to the app".

What is wrong

For a peer outside SWRP_TRUSTED_PROXIES, rproxy sets X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto itself and removes the other headers traefik sets. Any other forwarded header the peer sends reaches the app unchanged.

Seen with SWRP_TRUSTED_PROXIES set to empty, so that no peer is trusted. These arrived at the app as the client sent them, beside X-Forwarded-Proto: http from rproxy:

  • X-Forwarded-Scheme: https
  • X-Forwarded-Ssl: on
  • X-Forwarded-Protocol: https
  • X-Forwarded-Path
  • X-Forwarded-Client-Cert

Why it matters

Some apps read X-Forwarded-Ssl or X-Forwarded-Scheme to decide whether a request came over HTTPS. A client that connects to rproxy directly can make such an app take a plain request for a secure one.

The limits of it:

  • The client's address cannot be faked this way: X-Forwarded-For is always rproxy's own.
  • It needs a client that can reach rproxy without passing a trusted proxy. In the README's setups only traefik and the containers on its network can.
  • Behind traefik nothing changes: traefik is a trusted peer, so its headers pass on by design.

Acceptable

For a peer outside SWRP_TRUSTED_PROXIES, every header whose name starts with X-Forwarded- is removed before rproxy sets its three. X-Real-Ip and Forwarded are removed as now. SPEC.md says so, and a test sends the five names above.

AGENTS.md says forwarded headers count only when the peer is a trusted proxy; this applies it to every name with that prefix. Reversing it means restoring the fixed list of names.

Disclosures

  • Confidence: high that it happens. That it is worth fixing is a judgement call.
  • Not checked: which apps read which of these names, and whether traefik removes them from a visitor's request.
  • Left alone on purpose: the rarer names without the X-Forwarded- prefix (X-Url-Scheme, Front-End-Https, X-Original-Forwarded-For), which also pass.

Definition of done

  • One PR to next.
  • make check passes.

Model: fable-5-2

Not blocking: need not be fixed before real traffic. Found by the end-to-end check of `sww rproxy` (https://git.eeqj.de/sneak/smallwebwaf/issues/321) on `next` at `f7e3190`. ## Where - `internal/rproxy/client.go`, `setForwardedHeaders`. - `SPEC.md`, "Passing a request to the app". ## What is wrong For a peer outside `SWRP_TRUSTED_PROXIES`, `rproxy` sets `X-Forwarded-For`, `X-Forwarded-Host` and `X-Forwarded-Proto` itself and removes the other headers traefik sets. Any other forwarded header the peer sends reaches the app unchanged. Seen with `SWRP_TRUSTED_PROXIES` set to empty, so that no peer is trusted. These arrived at the app as the client sent them, beside `X-Forwarded-Proto: http` from `rproxy`: - `X-Forwarded-Scheme: https` - `X-Forwarded-Ssl: on` - `X-Forwarded-Protocol: https` - `X-Forwarded-Path` - `X-Forwarded-Client-Cert` ## Why it matters Some apps read `X-Forwarded-Ssl` or `X-Forwarded-Scheme` to decide whether a request came over HTTPS. A client that connects to `rproxy` directly can make such an app take a plain request for a secure one. The limits of it: - The client's address cannot be faked this way: `X-Forwarded-For` is always `rproxy`'s own. - It needs a client that can reach `rproxy` without passing a trusted proxy. In the README's setups only traefik and the containers on its network can. - Behind traefik nothing changes: traefik is a trusted peer, so its headers pass on by design. ## Acceptable For a peer outside `SWRP_TRUSTED_PROXIES`, every header whose name starts with `X-Forwarded-` is removed before `rproxy` sets its three. `X-Real-Ip` and `Forwarded` are removed as now. `SPEC.md` says so, and a test sends the five names above. `AGENTS.md` says forwarded headers count only when the peer is a trusted proxy; this applies it to every name with that prefix. Reversing it means restoring the fixed list of names. ## Disclosures - Confidence: high that it happens. That it is worth fixing is a judgement call. - Not checked: which apps read which of these names, and whether traefik removes them from a visitor's request. - Left alone on purpose: the rarer names without the `X-Forwarded-` prefix (`X-Url-Scheme`, `Front-End-Https`, `X-Original-Forwarded-For`), which also pass. ## Definition of done - One PR to `next`. - `make check` passes. Model: fable-5-2
Author
Collaborator

Plan. Build the "Acceptable" section above as written: for a peer outside SWRP_TRUSTED_PROXIES, every header whose name starts with X-Forwarded- is removed before rproxy sets its three; a trusted peer's headers pass on as now. This applies the iron rule in AGENTS.md that forwarded headers count only from a trusted proxy. The names without the prefix stay out of scope, as the issue says.

Package: internal/rproxy (client.go, setForwardedHeaders), whose doc says it is the reverse proxy; SPEC.md "Passing a request to the app" says so. The test sends the five names from an untrusted peer and checks none reaches the app, and that a trusted peer's still do.

Follow AGENTS.md: checks only through make targets. make fmt, then flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check; a flock timeout also exits 1, so read the output. Edit by hand. Title ending (closes #325). Label the PR needs-review.

Model: opus-5-5

**Plan.** Build the "Acceptable" section above as written: for a peer outside `SWRP_TRUSTED_PROXIES`, every header whose name starts with `X-Forwarded-` is removed before `rproxy` sets its three; a trusted peer's headers pass on as now. This applies the iron rule in `AGENTS.md` that forwarded headers count only from a trusted proxy. The names without the prefix stay out of scope, as the issue says. Package: `internal/rproxy` (`client.go`, `setForwardedHeaders`), whose doc says it is the reverse proxy; `SPEC.md` "Passing a request to the app" says so. The test sends the five names from an untrusted peer and checks none reaches the app, and that a trusted peer's still do. Follow `AGENTS.md`: checks only through `make` targets. `make fmt`, then `flock -w 1800 /srv/code/tmp/.gate-smallwebwaf.lock make check`; a `flock` timeout also exits 1, so read the output. Edit by hand. Title ending ` (closes #325)`. Label the PR `needs-review`. Model: opus-5-5
clawbot self-assigned this 2026-11-11 04:11:37 +01:00
Author
Collaborator

Built in #330. For a peer outside SWRP_TRUSTED_PROXIES, every X-Forwarded- header is now removed before rproxy sets its three. X-Real-IP and Forwarded are removed as before. SPEC.md says so. The test sends the five names from this issue, plus one made-up name in lower case.

Model: opus-5-5

Built in https://git.eeqj.de/sneak/smallwebwaf/pulls/330. For a peer outside `SWRP_TRUSTED_PROXIES`, every `X-Forwarded-` header is now removed before `rproxy` sets its three. `X-Real-IP` and `Forwarded` are removed as before. `SPEC.md` says so. The test sends the five names from this issue, plus one made-up name in lower case. Model: opus-5-5
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: sneak/smallwebwaf#325