From 8112e6328fd64a49a32bd4727b2ecb1b9799139f Mon Sep 17 00:00:00 2001 From: Kadir Can Ozden <101993364+bysiber@users.noreply.github.com> Date: Fri, 20 Feb 2026 18:35:46 +0300 Subject: [PATCH 1/2] Fix substring check used instead of equality for header names The `in` operator on `bytes` performs substring search, not equality. `header[0] in b"cookie"` matches any header name that is a substring of "cookie" (e.g. b"co", b"ok", b"e"), not just b"cookie" itself. This means short header names that happen to be substrings of "cookie" get incorrectly promoted to NeverIndexedHeaderTuple when their value is under 20 bytes, potentially affecting HPACK compression behavior. Changed both occurrences to use `==` for exact comparison: - Line 91: cookie header check in _secure_headers - Line 350: :method pseudo-header check in _reject_pseudo_header_fields --- src/h2/utilities.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/h2/utilities.py b/src/h2/utilities.py index 9b492210..7709aa60 100644 --- a/src/h2/utilities.py +++ b/src/h2/utilities.py @@ -88,7 +88,7 @@ def _secure_headers(headers: Iterable[Header], """ for header in headers: assert isinstance(header[0], bytes) - if header[0] in _SECURE_HEADERS or (header[0] in b"cookie" and len(header[1]) < 20): + if header[0] in _SECURE_HEADERS or (header[0] == b"cookie" and len(header[1]) < 20): yield NeverIndexedHeaderTuple(header[0], header[1]) else: yield header @@ -347,7 +347,7 @@ def _reject_pseudo_header_fields(headers: Iterable[Header], msg = f"Received custom pseudo-header field {header[0]!r}" raise ProtocolError(msg) - if header[0] in b":method": + if header[0] == b":method": method = header[1] else: From 5c29f140199460e15e06ccbd1dd684a0f377a7fb Mon Sep 17 00:00:00 2001 From: Kadir Can Ozden <101993364+bysiber@users.noreply.github.com> Date: Sat, 21 Feb 2026 21:47:08 +0300 Subject: [PATCH 2/2] Add regression tests for substring check fix - Add test data with header names that are substrings of 'cookie' but not equal to 'cookie' to verify they are not treated as sensitive - Add test for extract_method_header to verify substring names like ':me' do not falsely match ':method' --- tests/test_header_indexing.py | 37 +++++++++++++++++++++++++++++++++ tests/test_utility_functions.py | 13 ++++++++++++ 2 files changed, 50 insertions(+) diff --git a/tests/test_header_indexing.py b/tests/test_header_indexing.py index f50f23d8..252083e6 100644 --- a/tests/test_header_indexing.py +++ b/tests/test_header_indexing.py @@ -418,6 +418,19 @@ class TestSecureHeaders: HeaderTuple(b"cookie", b"twenty byte cookie!!"), HeaderTuple(b"Cookie", b"twenty byte cookie!!"), ] + # Headers with names that are substrings of "cookie" but not equal to + # "cookie". Before the fix, the ``in`` operator was used instead of + # ``==``, which caused these to be incorrectly treated as sensitive. + non_cookie_substring_headers = [ + (b"cook", b"short"), + (b"okie", b"short"), + (b"oo", b"short"), + (b"cooki", b"short"), + (b"ookie", b"short"), + HeaderTuple(b"cook", b"short"), + HeaderTuple(b"okie", b"short"), + HeaderTuple(b"cooki", b"short"), + ] server_config = h2.config.H2Configuration(client_side=False) @@ -622,3 +635,27 @@ def test_long_cookie_headers_can_be_indexed_push(self, ) assert c.data_to_send() == expected_frame.serialize() + + @pytest.mark.parametrize( + "headers", [example_request_headers, bytes_example_request_headers], + ) + @pytest.mark.parametrize("header", non_cookie_substring_headers) + def test_non_cookie_substring_headers_can_be_indexed(self, + headers, + header, + frame_factory) -> None: + """ + Headers with names that are substrings of 'cookie' but not equal to + 'cookie' should not be treated as sensitive. + """ + send_headers = [*headers, header] + expected_headers = [*headers, HeaderTuple(header[0], header[1])] + + c = h2.connection.H2Connection() + c.initiate_connection() + + c.clear_outbound_data_buffer() + c.send_headers(1, send_headers) + + f = frame_factory.build_headers_frame(headers=expected_headers) + assert c.data_to_send() == f.serialize() \ No newline at end of file diff --git a/tests/test_utility_functions.py b/tests/test_utility_functions.py index 31634f40..364fe968 100644 --- a/tests/test_utility_functions.py +++ b/tests/test_utility_functions.py @@ -163,6 +163,19 @@ def test_extract_header_method(self) -> None: self.example_headers_with_bytes, ) == b"GET" + def test_extract_method_header_no_false_substring_match(self) -> None: + """ + Headers with names that are substrings of ':method' but not equal + to ':method' should not be matched. + """ + headers = [ + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":me", b"GET"), + ] + assert extract_method_header(headers) is None + def test_size_limit_dict_limit() -> None: dct = SizeLimitDict(size_limit=2)