Support RFC 7239 Forwarded header and X-Forwarded-Host#2928
Support RFC 7239 Forwarded header and X-Forwarded-Host#2928
Forwarded header and X-Forwarded-Host#2928Conversation
Adds a new `--proxy-headers` mode option for selecting between the existing `X-Forwarded-*` family (default `x-forwarded`) and the standardized `Forwarded` header from RFC 7239 (new `forwarded`). Modes are mutually exclusive: when one is selected, the other family is ignored entirely so a client cannot smuggle values via whichever family the proxy is not configured for. The `x-forwarded` mode also now reads `X-Forwarded-Host` and rewrites the request `Host` header. `scope["server"]` is left untouched per the ASGI spec (it is the local bind, not a client-perceived host). CLI is backwards compatible: - `--proxy-headers` (bare) means `x-forwarded`, matching the prior bool - `--no-proxy-headers` disables proxy header parsing - `--proxy-headers x-forwarded|forwarded` selects the family
|
📖 Docs preview: https://6e000771.uvicorn.pages.dev |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fec7b4cc4a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| "--proxy-headers", | ||
| type=click.Choice(["x-forwarded", "forwarded"]), | ||
| is_flag=False, | ||
| flag_value="x-forwarded", | ||
| default="x-forwarded", |
There was a problem hiding this comment.
Restore bare
--proxy-headers compatibility
The new option definition still makes Click treat --proxy-headers as requiring a value, so the documented/claimed backward-compatible form now fails (python -m uvicorn --proxy-headers returns Option '--proxy-headers' requires an argument). This is a breaking CLI regression for existing scripts and deployments that previously passed the boolean flag without an explicit mode.
Useful? React with 👍 / 👎.
It is a tricky choice. last-win behavior is better for security but can break things when multiple reverse proxies are used. See this example #2310 (comment) It should be made apparent in the doc that if multiple reverse proxies are used, only the first one should define Forwarded header (other proxies should be configured with "IfNone" behavior for example for HAProxy). Default value for HAProxy is "append" which creates the issues described |
Closes #2237. Also closes #965 (rolls in the
X-Forwarded-Hostwork that was paused as #2922).Summary
--proxy-headersmode option to choose between the existingX-Forwarded-*family (defaultx-forwarded) and the standardizedForwardedheader from RFC 7239 (newforwarded).x-forwardedmode also now readsX-Forwarded-Hostand rewrites the requestHostheader.scope["server"]is intentionally left untouched per the ASGI spec.forwarded_allow_ips(default127.0.0.1) - headers are honored only when the connecting peer is in the allowlist.CLI is backwards compatible
x-forwardedmode, enabled--proxy-headersx-forwardedmode, enabled (matches the prior boolean)--no-proxy-headers--proxy-headers x-forwarded--proxy-headers forwardedImplementation notes
ProxyHeadersMiddlewareis a thin dispatcher; per-mode logic lives in_XForwardedParserand_RFC7239Parser(gzip-style pattern).X-Forwarded-For: rightmost-untrusted with leftmost fallback when all hops are trusted.for=unknownand_obfuscatedplaceholders are filtered before the walk._join_fieldcorrectly handles repeated headers (RFC 7230 §3.2.2). For single-valued headers (X-Forwarded-Proto,X-Forwarded-Host),_last_fieldreturns the rightmost value - this matches the priordict(scope["headers"])last-wins behavior and is the correct direction when a trusted proxy appends.Test plan
tests/middleware/test_proxy_headers.py- 41 new tests covering RFC 7239 parsing, hop selection, mode mutual exclusion, multi-header join, IPv6, placeholder filtering, duplicate-param smuggling defense, programmatic Config wiring.X-Forwarded-For/-Prototests unchanged - no behavior regression.mypy uvicornclean.ruff check .clean.AI Disclaimer
This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.