Skip to content

Reject invalid :method and :path in HTTP/2 requests - #747

Merged
mtrudel merged 1 commit into
mtrudel:mainfrom
neilberkman:h2-validate-method-path
Nov 11, 2026
Merged

mtrudel merged 1 commit into
mtrudel:mainfrom
neilberkman:h2-validate-method-path

Conversation

@neilberkman

Copy link
Copy Markdown
Contributor

HTTP/1 rejects a request line whose method is not a token or whose request-target contains control bytes or whitespace (#713). HTTP/2 did not apply the same checks to the :method and :path pseudo-headers: :method was taken as is and :path was checked only for a leading / or *. The field-value check already rejects control bytes and DEL in both, but it allows internal SP and HTAB and does not check token syntax. A :method of "", "GE T" or "GET/1", or a :path of "/a b", reached the Plug.Conn over HTTP/2 while the same bytes get a 400 over HTTP/1.

This change rejects, with a stream error of type PROTOCOL_ERROR, a :method that is not a non-empty token (RFC9110§9.1) and a :path containing SP or HTAB (RFC9113§8.3.1). The :path check is the HTTP/1 request-line check from #713. It moves from Bandit.HTTP1.Socket into Bandit.Headers unchanged, together with the is_tchar guard, so both protocols use one definition; field_value_valid?/1 is already shared the same way. Non-ASCII bytes in :path are still accepted, as they are over HTTP/1.

Eight tests in a new block in test/bandit/http2/protocol_test.exs: :method containing a space, a tab, a delimiter, a non-ASCII byte, and empty; :path containing a space and a tab; and a positive case for GET, PURGE and M-SEARCH with percent-encoded and non-ASCII paths. The seven negative cases fail without the change.

Verified with mix format --check-formatted, mix compile --warnings-as-errors, mix credo --strict, mix dialyzer, and mix test on Elixir 1.20.4 / OTP 29 (1082 passed, 1 skipped, 8 excluded; the 3 failures are in HibernationTest and also fail on main under OTP 29). I did not run h2spec locally.

HTTP/1 rejects a request line whose method is not a token or whose
request-target contains control bytes or whitespace (mtrudel#713). HTTP/2 took
:method as is and checked :path only for a leading / or *. The
field-value check already rejects control bytes and DEL in both, but it
allows internal SP and HTAB and says nothing about token syntax, so a
:method of "", "GE T" or "GET/1" or a :path of "/a b" reached the
Plug.Conn.

RFC9113§8.3.1 requires a valid value for each, so reject a :method that
is not a token (RFC9110§9.1) and a :path containing SP or HTAB with a
stream PROTOCOL_ERROR. The :path check is the HTTP/1 request line
check, now shared through Bandit.Headers along with the tchar guard, so
non-ASCII bytes are still accepted by both.
@mtrudel
mtrudel merged commit 1a3920e into mtrudel:main Nov 11, 2026
25 of 29 checks passed
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.

2 participants