Skip to content

Comments

fix: canonicalize Host header into :authority for outbound HTTP/2#877

Open
magurotuna wants to merge 2 commits intohyperium:masterfrom
magurotuna:canonicalize-authority-and-host
Open

fix: canonicalize Host header into :authority for outbound HTTP/2#877
magurotuna wants to merge 2 commits intohyperium:masterfrom
magurotuna:canonicalize-authority-and-host

Conversation

@magurotuna
Copy link
Contributor

@magurotuna magurotuna commented Feb 12, 2026

When sending HTTP/2 requests or push promises, promote the first valid Host header value to :authority and retain it on the wire. Duplicate Host values are collapsed to a single entry matching :authority, and unparseable Host values are stripped. This prevents emitting conflicting :authority and Host while keeping the Host header available for intermediaries that may need it (RFC 9113 §8.3.1).
The "Host wins" semantics align with the behavior of other HTTP/2 client implementations such as curl, Go's std, and Python's httpx.

Fixes #876

When sending HTTP/2 requests or push promises, promote the first valid
Host header value to :authority and strip all Host headers from regular
fields. This prevents emitting conflicting :authority and Host on the
wire, aligning with RFC 9113 and the behavior of other HTTP/2 client
implementations such as curl, Go's std, and Python's httpx.

Fixes hyperium#876
@seanmonstar
Copy link
Member

@cratelyn @olix0r I feel like we considered this a long time ago, and for transparent proxy purposes, chose not to normalize? But I can't remember if we decided that explicitly, or not. Checking if you have cases where this would be problematic.

@cratelyn
Copy link
Member

@cratelyn @olix0r I feel like we considered this a long time ago, and for transparent proxy purposes, chose not to normalize? But I can't remember if we decided that explicitly, or not. Checking if you have cases where this would be problematic.

@seanmonstar i wasn't around for that earlier consideration, but i pinged Oliver to take a look at this. i suspect he'll be better positioned to remember the details here 🙂 thank you for checking in with us!

@cratelyn
Copy link
Member

cratelyn commented Feb 18, 2026

hi @seanmonstar, thank you for your patience.

looking at what this function does...

/// Canonicalize `Host` header into `:authority` pseudo-header for HTTP/2.
///
/// - If a `Host` header is present, attempt to parse its first value as a URI authority.
/// - On success, override `:authority` with the parsed value.
/// - Always remove all `Host` headers from the regular header map.

i believe the relevant portions of RFC 9113 are found in section 8.3.1. the strict rules i see for clients are:

  • Clients that generate HTTP/2 requests directly MUST use the ":authority" pseudo-header field
  • Clients MUST NOT generate a request with a Host header field that differs from the ":authority" pseudo-header field

so host headers are fine for clients to emit, so long as they do not differ from the ":authority" pseudo-header. removing them unconditionally would, i think, be going a step too far.

for intermediaries, the rules i see are:

  • An intermediary that forwards a request over HTTP/2 MUST construct an ":authority" pseudo-header field using the authority information from the control data of the original request, unless the original request's target URI does not contain authority information (in which case it MUST NOT generate ":authority"
  • An intermediary that forwards a request over HTTP/2 MAY retain any Host header field.
  • An intermediary that needs to generate a Host header field (which might be necessary to construct an HTTP/1.1 request) MUST use the value from the ":authority" pseudo-header field as the value of the Host field, unless the intermediary also changes the request target. This replaces any existing Host field to avoid potential vulnerabilities in HTTP routing.

i think it's reasonable for h2 to enforce RFC 9113 compliance, and avoiding mismatched :authority and Host values seems like a wise decision, particularly because these MUST NOT differ.

i don't think that we should always remove all Host headers, however. nothing says clients MUST NOT generate them, and intermediaries MAY retain them.

ideally, it'd be nice to leave a way for intermediaries to retain the host header field, per https://www.rfc-editor.org/rfc/rfc9113.html#section-8.3.1-2.3.6. that's likely important for intermediaries like us (linkerd) that transport HTTP/1 traffic over an HTTP/2 intermediary layer.

@magurotuna magurotuna force-pushed the canonicalize-authority-and-host branch 2 times, most recently from 353f6ca to 570641f Compare February 19, 2026 15:55
@magurotuna magurotuna force-pushed the canonicalize-authority-and-host branch from 570641f to b7a0bcb Compare February 19, 2026 16:01
@magurotuna
Copy link
Contributor Author

Thanks for the thorough review and the RFC analysis, @cratelyn! I've addressed your feedback:

  • Moved canonicalize_host_authority to the util section, after the struct definitions and impl blocks
  • Added RFC 9113 §8.3.1 references in the doc comment
  • Host is now retained on the wire instead of being stripped, so intermediaries can forward it. Duplicate values are collapsed to a single entry matching :authority, and unparseable values are stripped.

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.

Outbound HTTP/2 frames can emit conflicting :authority and host headers

4 participants