Repository navigation
Validate HTTP/2 request methods, targets and header fields - #545
Merged
ericmj merged 3 commits intoOct 25, 2026
Merged
Conversation
Mint.HTTP2.request/5 lowercased the header names it was given and HPACK-encoded everything else as is, and stream_request_body/3 did the same for trailers. A method, :path, CONNECT target, header name or header value containing CR, LF or NUL, a header name containing a space or a colon, or a value with leading or trailing whitespace was sent to the server unchanged. Mint.HTTP1 rejects all of these, and Mint.HTTP2 rejects them in responses since elixir-mint#504, so the behaviour of a request depended on which protocol ALPN negotiated. RFC 9113 8.2.1 forbids these characters in field names and values and says a sender must not generate such a message. HTTP/2 framing isn't confused by them, but an intermediary that forwards the request over HTTP/1.1 without re-validating the fields turns an injected CRLF into request header injection or request splitting on its back-end connection, so an application that is safe against an HTTP/1 origin isn't once the origin or a CDN in front of it enables HTTP/2. Requests and trailers are now validated before a stream is opened, with the field rules already used for responses: the method must be a token (RFC 9110 9.1), :path and the CONNECT :authority must be non-empty field values (RFC 9113 8.3.1), pseudo-header names passed by the caller must be valid field names, and trailers can't carry pseudo-headers (RFC 9113 8.1). Invalid input fails with :invalid_request_method, :invalid_request_target, :invalid_header_name or :invalid_header_value, like in Mint.HTTP1, and the connection stays open without the request taking up a stream. The messages for the two header errors no longer mention the response, since they now apply to requests as well.
Member
|
Thank you! 💜 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Mint.HTTP2.request/5lowercases the header names it is given and HPACK-encodes everything else unchanged, andstream_request_body/3does the same for trailers. A method,:path, CONNECT:authority, header name or header value containing CR, LF or NUL, a header name containing a space or a colon, or a value with leading or trailing whitespace is sent to the server as is.Mint.HTTP1rejects all of these, andMint.HTTP2rejects them in responses since #504, so whether a request is validated depends on which protocol ALPN negotiated.RFC 9113 8.2.1 forbids these characters in field names and values and says a sender must not generate such a message. HTTP/2 framing isn't confused by them, but an intermediary that forwards the request over HTTP/1.1 without re-validating the fields turns an injected CRLF into header injection or request splitting on its back-end connection. An application that is safe against an HTTP/1 origin stops being safe once the origin, or a CDN in front of it, enables HTTP/2.
Requests and trailers are now validated before a stream is opened, with the field rules already used for responses: the method must be a token (RFC 9110 9.1),
:pathand the CONNECT:authoritymust be non-empty field values (RFC 9113 8.3.1), pseudo-headers passed by the caller must have valid names and values, and trailers can't carry pseudo-headers (RFC 9113 8.1). Invalid input fails with:invalid_request_method,:invalid_request_target,:invalid_header_nameor:invalid_header_value, as inMint.HTTP1; nothing is sent and the request doesn't take up a stream. Theformat_error/1messages for the two header errors no longer say "in the response" since they apply to requests now too.:pathis checked with the RFC 9113 field-value rules, not the stricter HTTP/1 request-target grammar; doing the latter would need:skip_target_validationplumbing for HTTP/2 and I kept it out of this change. Header-name errors report the lowercased name.35 tests added to
conn_test.exs(invalid methods, targets, CONNECT targets, names, values, explicit pseudo-headers, trailers, and a positive case for empty values, obs-text and inner whitespace);mix test test/mint/http2/conn_test.exs303 passed. Fullmix test697/713, the 16 failures are the docker-backed integration tests and fail identically on main here.mix format --check-formatted,--warnings-as-errorsand dialyzer clean.Prepared with AI assistance (Claude); I reproduced the CRLF pass-through against the HTTP/2 test server on main and verified the new tests fail there.