Skip to content

Comments

Ignore illegal response header (#2439)#5

Open
MitchLewis930 wants to merge 1 commit intopr_055_beforefrom
pr_055_after
Open

Ignore illegal response header (#2439)#5
MitchLewis930 wants to merge 1 commit intopr_055_beforefrom
pr_055_after

Conversation

@MitchLewis930
Copy link

@MitchLewis930 MitchLewis930 commented Jan 30, 2026

PR_055

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Response header validation now properly filters out headers that violate Rack specification standards, ensuring improved HTTP compliance and security.
  • Tests

    • Previously skipped response header validation tests are now enabled and executed.

✏️ Tip: You can customize this high-level summary in your review settings.

* Ignore illegal response header
@coderabbitai
Copy link

coderabbitai bot commented Jan 30, 2026

📝 Walkthrough

Walkthrough

This pull request implements header validation for response headers to comply with the Rack specification. New regex-based validation constants are introduced, and the request processing logic is enhanced with methods to check header key and value legality, replacing the previous header injection detection approach. Corresponding tests are enabled to verify the new validation behavior.

Changes

Cohort / File(s) Summary
Release Notes
History.md
Added bugfix entry documenting the new illegal response header validation feature.
Header Validation Constants
lib/puma/const.rb
Removed HTTP_INJECTION_REGEX constant and added five new header-validation related constants: DQUOTE, HTTP_HEADER_DELIMITER, ILLEGAL_HEADER_KEY_REGEX, ILLEGAL_HEADER_VALUE_REGEX, and BANNED_HEADER_KEY.
Header Validation Logic
lib/puma/request.rb
Added illegal_header_key? and illegal_header_value? methods for header legality checks; replaced possible_header_injection? calls with the new illegal_header_value? method in str_early_hints and str_headers; added guard to skip BANNED_HEADER_KEY headers; updated private method declarations.
Header Validation Tests
test/test_response_header.rb
Removed skip directives from multiple test cases to enable execution of tests for Status key, rack-prefixed keys, illegal characters in header keys/values, early hints, content-length override, and newline handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Headers must dance in proper form,
No illegal chars to cause alarm!
New validation logic springs to life,
Trimming banned headers with precision's knife.
Rack spec blessed, we hop along! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description check ⚠️ Warning The pull request description is incomplete, containing only 'PR_055' without addressing required template sections like objectives, motivation, or the contributor checklist. Complete the description by filling out the template sections: describe the problem, link related issues, explain design choices, and check off applicable items in the contributor checklist.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding header validation to ignore illegal response headers as referenced in issue #2439.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pr_055_after

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/puma/request.rb (1)

341-355: ⚠️ Potential issue | 🟡 Minor

Apply banned-header filtering to Early Hints too.

str_early_hints currently checks illegal_header_key?(k) but unlike str_headers, it does not filter BANNED_HEADER_KEY (regex /rack.|status/). This creates an inconsistency where early hints responses could include Rack-reserved keys that are prohibited in regular response headers, violating Rack's response header contract.

Proposed fix
 def str_early_hints(headers)
   eh_str = "HTTP/1.1 103 Early Hints\r\n".dup
   headers.each_pair do |k, vs|
-    next if illegal_header_key?(k)
+    next if illegal_header_key?(k) || (BANNED_HEADER_KEY === k.to_s.downcase)
🤖 Fix all issues with AI agents
In `@lib/puma/const.rb`:
- Around line 241-248: BANNED_HEADER_KEY is too permissive and matches
substrings like "x-status-code" or "x-rack-id"; update the BANNED_HEADER_KEY
constant so it only matches header keys that are exactly "status" or that start
with the literal "rack." prefix per Rack spec (i.e. anchor the start and end and
use a literal dot for the prefix). Replace the existing /rack.|status/ pattern
with a regex that anchors and distinguishes the two cases (for example a pattern
that matches ^rack\..* for prefixed keys and ^status$ for the exact key) in the
BANNED_HEADER_KEY constant.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e4e5dc and 31c72cf.

📒 Files selected for processing (4)
  • History.md
  • lib/puma/const.rb
  • lib/puma/request.rb
  • test/test_response_header.rb
💤 Files with no reviewable changes (1)
  • test/test_response_header.rb
🔇 Additional comments (3)
lib/puma/request.rb (2)

285-299: LGTM: helper methods are concise and clear.


410-435: Header filtering logic looks solid.

History.md (1)

8-10: Release note addition looks good.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines +241 to +248
# Illegal character in the key or value of response header
DQUOTE = "\"".freeze
HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}").freeze
ILLEGAL_HEADER_KEY_REGEX = /[\u0000-\u0025|#{DQUOTE}|#{HTTP_HEADER_DELIMITER}]/.freeze
ILLEGAL_HEADER_VALUE_REGEX = /[\000-\037]/.freeze

# Banned keys of response header
BANNED_HEADER_KEY = /rack.|status/.freeze
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash

# Test the regex patterns with Ruby
ruby <<'EOF'
# Test current regex
current_regex = /rack.|status/

# Test proposed regex
proposed_regex = /\A(?:rack\..*|status)\z/

test_headers = [
  "rack.version",           # Should be banned (rack.)
  "rack.input",             # Should be banned (rack.)
  "status",                 # Should be banned (exact match)
  "x-status-code",          # Should NOT be banned
  "x-tracking-id",          # Should NOT be banned
  "x-rack-id",              # Should NOT be banned
  "content-status",         # Should NOT be banned
  "my-rack-value",          # Should NOT be banned (rack. not literal)
  "Status",                 # Should NOT be banned (case matters)
  "rack.",                  # Edge case - bare prefix
]

puts "=== Current Regex: /rack.|status/ ==="
test_headers.each do |header|
  match = current_regex.match(header)
  puts "#{header.ljust(20)} => #{match ? 'BANNED' : 'allowed'}"
end

puts "\n=== Proposed Regex: /\\A(?:rack\\..*|status)\\z/ ==="
test_headers.each do |header|
  match = proposed_regex.match(header)
  puts "#{header.ljust(20)} => #{match ? 'BANNED' : 'allowed'}"
end
EOF

Repository: Signal65/puma-CodeRabbit

Length of output: 783


🏁 Script executed:

# Check actual usage and tests of BANNED_HEADER_KEY
cd lib && rg -A5 -B5 "BANNED_HEADER_KEY" --type rb

Repository: Signal65/puma-CodeRabbit

Length of output: 94


🏁 Script executed:

# Search for tests related to header validation
cd .. && find . -name "*test*" -o -name "*spec*" | grep -E "\.(rb)$" | xargs rg -l "header|BANNED_HEADER" 2>/dev/null | head -10

Repository: Signal65/puma-CodeRabbit

Length of output: 219


🏁 Script executed:

# Look at the actual usage of BANNED_HEADER_KEY in the codebase
cd lib && rg "BANNED_HEADER_KEY" -B3 -A3

Repository: Signal65/puma-CodeRabbit

Length of output: 550


🏁 Script executed:

# Check test files for header validation
cat ./git/test/test_response_header.rb

Repository: Signal65/puma-CodeRabbit

Length of output: 134


🏁 Script executed:

# Also check the context where BANNED_HEADER_KEY is used
cat -n lib/puma/response.rb | head -50

Repository: Signal65/puma-CodeRabbit

Length of output: 120


🏁 Script executed:

# Search for where headers are validated using this constant
rg "BANNED_HEADER_KEY" lib/

Repository: Signal65/puma-CodeRabbit

Length of output: 182


🏁 Script executed:

# Find test files
find . -path "*/test*" -name "*.rb" | head -20

Repository: Signal65/puma-CodeRabbit

Length of output: 772


🏁 Script executed:

# Look at the request.rb usage context
cat -n lib/puma/request.rb | grep -A10 -B10 "BANNED_HEADER_KEY"

Repository: Signal65/puma-CodeRabbit

Length of output: 859


🏁 Script executed:

# Check if there are any tests for header validation
find . -name "*.rb" -path "*/test*" | xargs rg -l "header" 2>/dev/null | head -5

Repository: Signal65/puma-CodeRabbit

Length of output: 199


🏁 Script executed:

# Look at the response header test file
cat -n ./test/test_response_header.rb

Repository: Signal65/puma-CodeRabbit

Length of output: 4652


🏁 Script executed:

# Also check what the purpose of BANNED_HEADER_KEY is - search for RFC or comments about Rack headers
rg -B5 "BANNED_HEADER_KEY|rack\." lib/puma/const.rb

Repository: Signal65/puma-CodeRabbit

Length of output: 807


🏁 Script executed:

# Check if there are any tests that verify the banned header behavior
rg -B5 -A5 "banned|BANNED|rack\." ./test/test_response_header.rb

Repository: Signal65/puma-CodeRabbit

Length of output: 1012


BANNED_HEADER_KEY regex is too broad and silently drops valid headers.

The pattern /rack.|status/ matches any header containing status or rack followed by any character anywhere in the key. This causes valid headers like x-status-code, x-tracking-id, x-rack-id, and content-status to be incorrectly filtered out.

The regex should anchor to the full string and use a literal dot to match only rack. prefixed headers (per Rack specification) and the exact status header:

Proposed fix
-    BANNED_HEADER_KEY = /rack.|status/.freeze
+    BANNED_HEADER_KEY = /\A(?:rack\..*|status)\z/.freeze
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Illegal character in the key or value of response header
DQUOTE = "\"".freeze
HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}").freeze
ILLEGAL_HEADER_KEY_REGEX = /[\u0000-\u0025|#{DQUOTE}|#{HTTP_HEADER_DELIMITER}]/.freeze
ILLEGAL_HEADER_VALUE_REGEX = /[\000-\037]/.freeze
# Banned keys of response header
BANNED_HEADER_KEY = /rack.|status/.freeze
# Illegal character in the key or value of response header
DQUOTE = "\"".freeze
HTTP_HEADER_DELIMITER = Regexp.escape("(),/:;<=>?@[]{}").freeze
ILLEGAL_HEADER_KEY_REGEX = /[\u0000-\u0025|#{DQUOTE}|#{HTTP_HEADER_DELIMITER}]/.freeze
ILLEGAL_HEADER_VALUE_REGEX = /[\000-\037]/.freeze
# Banned keys of response header
BANNED_HEADER_KEY = /\A(?:rack\..*|status)\z/.freeze
🤖 Prompt for AI Agents
In `@lib/puma/const.rb` around lines 241 - 248, BANNED_HEADER_KEY is too
permissive and matches substrings like "x-status-code" or "x-rack-id"; update
the BANNED_HEADER_KEY constant so it only matches header keys that are exactly
"status" or that start with the literal "rack." prefix per Rack spec (i.e.
anchor the start and end and use a literal dot for the prefix). Replace the
existing /rack.|status/ pattern with a regex that anchors and distinguishes the
two cases (for example a pattern that matches ^rack\..* for prefixed keys and
^status$ for the exact key) in the BANNED_HEADER_KEY constant.

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