On this page
ADR-0040: A forwarded client address is honoured only behind a declared proxy
Status: Accepted (WP-70g, WP-70e). Implemented in crates/commonlayer-auth/src/http/middleware.rs (client_ip), apps/web/src/lib/api/forwarding.ts, the Helm chart (config.trustedProxies, web.behindTrustedProxy) and docker-compose.yml (CL_STACK_TRUSTED_PROXIES, CL_STACK_WEB_BEHIND_TRUSTED_PROXY).
Context
Per-IP rate limits (anonymous API, registration, sign-in, registry tokens) and audit
records need the client's address. Behind an ingress, the direct peer is the ingress;
for server-side rendering, the API's peer is the web server. The first version of the
Compose stack trusted the web container and let it forward the visitor's
X-Forwarded-For as received: in the publish-drill session a forged
X-Forwarded-For: 203.0.113.8 sent to the web container reached the API as the client
address through the /api rewrite, so any visitor could choose their rate-limit bucket.
Decision
- The API takes the client address from
X-Forwarded-Foronly when the direct peer is inserver.trusted_proxies, and then uses the right-most entry that is not itself a trusted proxy. Default: no trusted proxies. - The web server forwards a visitor's address on its server-side API calls only when it
is declared to run behind a trusted proxy (
BEHIND_TRUSTED_PROXY=true, chart valueweb.behindTrustedProxy), that is, when an ingress in front of it appends the real address. Off by default. - Compose trusts no proxy by default, so all portal traffic in
make stackcounts against the web container's address. The e2e workflow's stack trusts the web container and lets it forward, in that stack only, so per-test addresses work.
Consequences
- A deployment that wants per-visitor limits for portal traffic must run an ingress that
appends the client address, list the web pods (or the ingress) in
server.trusted_proxies, and setweb.behindTrustedProxy. Without an ingress in front of the portal a client could again choose its own bucket, so production must keep one. - The behaviour was verified against the Compose stack with forged headers (WP-70g): defaults → the web container's address; e2e settings → the forwarded address. The Helm settings are covered by chart unit tests, not by a cluster install.
- ADR-0012 said the web server must be listed in
server.trusted_proxies; that is now conditional on the above.