From 820f0b986368c0507cdce2d0fdd1d6c3be0793b2 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Nov 2026 20:30:25 +1300 Subject: [PATCH 1/3] Record why received trailers are not marked with `trailer!` in a `SECURITY:` comment. --- lib/async/http/protocol/http2/stream.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/async/http/protocol/http2/stream.rb b/lib/async/http/protocol/http2/stream.rb index 2ca4207c..3ccb55e5 100644 --- a/lib/async/http/protocol/http2/stream.rb +++ b/lib/async/http/protocol/http2/stream.rb @@ -57,6 +57,8 @@ def add_header(key, value, trailer: false) end # Process trailing headers received after the body. + # + # SECURITY: Received trailers are added to the same headers object as the initial headers and are not marked with `Headers#trailer!`. This is not a smuggling vector: `add(..., trailer: true)` rejects any field whose policy does not permit it as a trailer, so a field such as `content-length` cannot arrive here, and position in the message does not change what a permitted field means. Marking them would make a buffering proxy re-send them as trailers and drop them from non-chunked responses. Whether the `Generic` fallback policy should permit unknown fields as trailers is tracked in socketry/protocol-http#127. # @parameter headers [Array] The trailing header key-value pairs. # @parameter end_stream [Boolean] Whether the stream ends after these headers. def receive_trailing_headers(headers, end_stream) From aff27a14e736a689cbf9365c402566f786ea34dd Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Nov 2026 22:34:16 +1300 Subject: [PATCH 2/3] Move the `SECURITY:` comment to the `add_header` call it explains. --- lib/async/http/protocol/http2/stream.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/async/http/protocol/http2/stream.rb b/lib/async/http/protocol/http2/stream.rb index 3ccb55e5..bb39b304 100644 --- a/lib/async/http/protocol/http2/stream.rb +++ b/lib/async/http/protocol/http2/stream.rb @@ -57,12 +57,11 @@ def add_header(key, value, trailer: false) end # Process trailing headers received after the body. - # - # SECURITY: Received trailers are added to the same headers object as the initial headers and are not marked with `Headers#trailer!`. This is not a smuggling vector: `add(..., trailer: true)` rejects any field whose policy does not permit it as a trailer, so a field such as `content-length` cannot arrive here, and position in the message does not change what a permitted field means. Marking them would make a buffering proxy re-send them as trailers and drop them from non-chunked responses. Whether the `Generic` fallback policy should permit unknown fields as trailers is tracked in socketry/protocol-http#127. # @parameter headers [Array] The trailing header key-value pairs. # @parameter end_stream [Boolean] Whether the stream ends after these headers. def receive_trailing_headers(headers, end_stream) headers.each do |key, value| + # SECURITY: Received trailers join the same headers object as the initial headers and are not marked with `trailer!`. This is not a smuggling vector: pseudo-headers are rejected above, `trailer: true` rejects any field whose policy does not permit it as a trailer, so a field such as `content-length` cannot arrive here, and position in the message does not change what a permitted field means. Marking them would make a buffering proxy re-send them as trailers and drop them from non-chunked responses. Whether the `Generic` fallback policy should permit unknown fields as trailers is tracked in socketry/protocol-http#127. add_header(key, value, trailer: true) end end From d89ee9cbe153810f4194d27199a12b2ffc63b9be Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Nov 2026 22:37:00 +1300 Subject: [PATCH 3/3] Shorten the `SECURITY:` comment. --- lib/async/http/protocol/http2/stream.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/async/http/protocol/http2/stream.rb b/lib/async/http/protocol/http2/stream.rb index bb39b304..ab2d55d4 100644 --- a/lib/async/http/protocol/http2/stream.rb +++ b/lib/async/http/protocol/http2/stream.rb @@ -61,7 +61,7 @@ def add_header(key, value, trailer: false) # @parameter end_stream [Boolean] Whether the stream ends after these headers. def receive_trailing_headers(headers, end_stream) headers.each do |key, value| - # SECURITY: Received trailers join the same headers object as the initial headers and are not marked with `trailer!`. This is not a smuggling vector: pseudo-headers are rejected above, `trailer: true` rejects any field whose policy does not permit it as a trailer, so a field such as `content-length` cannot arrive here, and position in the message does not change what a permitted field means. Marking them would make a buffering proxy re-send them as trailers and drop them from non-chunked responses. Whether the `Generic` fallback policy should permit unknown fields as trailers is tracked in socketry/protocol-http#127. + # SECURITY: Trailers join the headers unmarked. This is not a smuggling vector: `trailer: true` admits only fields whose policy permits trailers, and position does not change what those fields mean. add_header(key, value, trailer: true) end end