Skip to content

Support RFC 7239 Forwarded header and X-Forwarded-Host#2928

Open
Kludex wants to merge 2 commits intomainfrom
forwarded-header
Open

Support RFC 7239 Forwarded header and X-Forwarded-Host#2928
Kludex wants to merge 2 commits intomainfrom
forwarded-header

Conversation

@Kludex
Copy link
Copy Markdown
Owner

@Kludex Kludex commented Apr 30, 2026

Closes #2237. Also closes #965 (rolls in the X-Forwarded-Host work that was paused as #2922).

Summary

  • New --proxy-headers mode option to choose 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. Silent fallback would let a client smuggle values via whichever family the proxy is not configured for.
  • The default x-forwarded mode also now reads X-Forwarded-Host and rewrites the request Host header. scope["server"] is intentionally left untouched per the ASGI spec.
  • Trust gating is unchanged: forwarded_allow_ips (default 127.0.0.1) - headers are honored only when the connecting peer is in the allowlist.

CLI is backwards compatible

Invocation Behavior
(default) x-forwarded mode, enabled
--proxy-headers x-forwarded mode, enabled (matches the prior boolean)
--no-proxy-headers disabled
--proxy-headers x-forwarded new explicit form
--proxy-headers forwarded RFC 7239 mode

Implementation notes

  • ProxyHeadersMiddleware is a thin dispatcher; per-mode logic lives in _XForwardedParser and _RFC7239Parser (gzip-style pattern).
  • RFC 7239 parser handles quoted-strings, backslash escapes, IPv6 brackets, and rejects forwarded-elements with duplicate parameters (smuggling defense).
  • Hop selection mirrors X-Forwarded-For: rightmost-untrusted with leftmost fallback when all hops are trusted. for=unknown and _obfuscated placeholders are filtered before the walk.
  • _join_field correctly handles repeated headers (RFC 7230 §3.2.2). For single-valued headers (X-Forwarded-Proto, X-Forwarded-Host), _last_field returns the rightmost value - this matches the prior dict(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.
  • Existing X-Forwarded-For/-Proto tests unchanged - no behavior regression.
  • mypy uvicorn clean.
  • ruff check . clean.

AI Disclaimer

This PR was developed with the assistance of either Claude or Codex. I've reviewed and verified the changes.

Kludex added 2 commits April 30, 2026 19:52
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
@github-actions
Copy link
Copy Markdown
Contributor

📖 Docs preview: https://6e000771.uvicorn.pages.dev

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 30, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing forwarded-header (fec7b4c) with main (6761b2c)

Open in CodSpeed

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread uvicorn/main.py
Comment on lines +226 to +230
"--proxy-headers",
type=click.Choice(["x-forwarded", "forwarded"]),
is_flag=False,
flag_value="x-forwarded",
default="x-forwarded",
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

@ptrovatelli
Copy link
Copy Markdown

ptrovatelli commented May 5, 2026

_last_field returns the rightmost value - this matches the prior dict(scope["headers"]) last-wins behavior and is the correct direction when a trusted proxy appends.

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
FYI the other solutions i've seen around is the definition of a "max number of trusted proxies" so that each proxy can add values and the application server will go up the chain only to a max number of header definition to avoid header poisoning by the end user

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add support for Fowarded header (RFC 7239) Support X-Forwarded-Host in proxy-headers

2 participants