From fda76fa92846c62ea9b9c4b3f0dfedb0b47cb54f Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Nov 2026 20:30:17 +1300 Subject: [PATCH 1/3] Record why received trailers are not marked with `trailer!` in a `SECURITY:` comment. --- lib/protocol/http1/body/chunked.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/protocol/http1/body/chunked.rb b/lib/protocol/http1/body/chunked.rb index 2c8b19e..18630c8 100644 --- a/lib/protocol/http1/body/chunked.rb +++ b/lib/protocol/http1/body/chunked.rb @@ -148,6 +148,8 @@ def as_json(...) private # Read the trailer from the connection, and add any headers to the trailer. + # + # SECURITY: Received trailers are added to the same headers object as the header section 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` or `host` 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. def read_trailer while line = @connection.read_line # Empty line indicates end of trailer: From ceae8f62b21c35e93cfd18138646d4ffe504eda4 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Nov 2026 22:34:12 +1300 Subject: [PATCH 2/3] Move the `SECURITY:` comment to the `add` call it explains. --- lib/protocol/http1/body/chunked.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/protocol/http1/body/chunked.rb b/lib/protocol/http1/body/chunked.rb index 18630c8..c633460 100644 --- a/lib/protocol/http1/body/chunked.rb +++ b/lib/protocol/http1/body/chunked.rb @@ -148,14 +148,13 @@ def as_json(...) private # Read the trailer from the connection, and add any headers to the trailer. - # - # SECURITY: Received trailers are added to the same headers object as the header section 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` or `host` 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. def read_trailer while line = @connection.read_line # Empty line indicates end of trailer: break if line.empty? if match = line.match(HEADER) + # SECURITY: Received trailers join the same headers object as the header section and are not marked with `trailer!`. This is not a smuggling vector: `trailer: true` rejects any field whose policy does not permit it as a trailer, so a field such as `content-length` or `host` 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. @headers.add(match[1], match[2], trailer: true) else raise BadHeader, "Could not parse header: #{line.inspect}" From edb6cc8fff9a0e4be62c9cff515f8ee4a0e29755 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Thu, 5 Nov 2026 22:36:56 +1300 Subject: [PATCH 3/3] Shorten the `SECURITY:` comment. --- lib/protocol/http1/body/chunked.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/protocol/http1/body/chunked.rb b/lib/protocol/http1/body/chunked.rb index c633460..bce9c4e 100644 --- a/lib/protocol/http1/body/chunked.rb +++ b/lib/protocol/http1/body/chunked.rb @@ -154,7 +154,7 @@ def read_trailer break if line.empty? if match = line.match(HEADER) - # SECURITY: Received trailers join the same headers object as the header section and are not marked with `trailer!`. This is not a smuggling vector: `trailer: true` rejects any field whose policy does not permit it as a trailer, so a field such as `content-length` or `host` 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. @headers.add(match[1], match[2], trailer: true) else raise BadHeader, "Could not parse header: #{line.inspect}"