Skip to content

Enforce W3C Baggage size and entry-count caps in Parser#1975

Merged
bobstrecansky merged 1 commit into
open-telemetry:mainfrom
tonghuaroot:fix-baggage-extract-cap
May 28, 2026
Merged

Enforce W3C Baggage size and entry-count caps in Parser#1975
bobstrecansky merged 1 commit into
open-telemetry:mainfrom
tonghuaroot:fix-baggage-extract-cap

Conversation

@tonghuaroot

Copy link
Copy Markdown
Contributor

Fixes #1974.

What

BaggagePropagator::extract walked the entire explode(',', $header)
result with no cap on header byte size, no cap on entry count, and no
per-entry length cap. This change adds two private constants to
Parser and enforces them inside parseInto.

Change

  • Parser::MAX_BAGGAGE_BYTES = 8192
  • Parser::MAX_BAGGAGE_ENTRIES = 180
  • parseInto returns early when
    strlen($header) > MAX_BAGGAGE_BYTES (cheap, prevents the per-entry
    allocation loop running at all).
  • parseInto breaks once MAX_BAGGAGE_ENTRIES accepted entries
    have been processed.

Values are taken from the W3C Baggage spec
(#limits) and match what
the other SDKs already do:

SDK max bytes max entries
java (1.62.0) 8192 180
go 8192 64
dotnet 8192 180
cpp 8192 180

Tests

Two regression tests added in tests/Unit/API/Baggage/Propagation/ParserTest.php:

  • test_parse_into_rejects_header_exceeding_w3c_byte_limit builds a
    ~10 KiB header from 1024 pairs and asserts the builder is never
    invoked.
  • test_parse_into_caps_entries_at_w3c_list_member_limit builds a
    500-pair header (under 8192 bytes) and asserts the builder receives
    exactly 180 set calls.

vendor/bin/phpunit tests/Unit/API/Baggage passes 63/63 locally on
PHP 8.5.6.

Static analysis

  • vendor/bin/phpstan analyse src/API/Baggage/Propagation/Parser.php
    no errors.
  • vendor-bin/rector/vendor/bin/rector process ... --dry-run no
    changes.
  • vendor-bin/php-cs-fixer/vendor/bin/php-cs-fixer fix ... --dry-run
    no changes.

Context

Originally filed as a private security advisory
(GHSA-7f5h-mpj3-v3hf,
now closed). Per the project security policy the maintainers prefer
to handle this through the standard issue/PR workflow as a hardening
bug.

References

The W3C Baggage specification recommends a max of 8192 total bytes and
180 list-members. `Parser::parseInto` walked the entire
`explode(',', $header)` with no cap on header byte size, no cap on
entry count, and no per-entry length cap, allowing a single inbound
HTTP request with an oversized `baggage` header to consume linear
CPU/memory in PHP-FPM workers.

This change adds two private constants `MAX_BAGGAGE_BYTES` (8192) and
`MAX_BAGGAGE_ENTRIES` (180), rejects the whole header up-front when
`strlen($header) > MAX_BAGGAGE_BYTES`, and breaks the parse loop once
the entry cap is reached. Mirrors the inject-side caps already in the
go, dotnet, cpp, and (post-1.62.0) java SDKs.

Background context: GHSA-7f5h-mpj3-v3hf (closed advisory; treated as a
hardening bug per the project security policy).

Refs: https://www.w3.org/TR/baggage/#limits
@tonghuaroot
tonghuaroot requested a review from a team as a code owner May 27, 2026 01:30
@codecov

codecov Bot commented May 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 67.73%. Comparing base (29066b4) to head (1a83cd8).
⚠️ Report is 1 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff              @@
##               main    #1975      +/-   ##
============================================
- Coverage     67.73%   67.73%   -0.01%     
- Complexity     3044     3046       +2     
============================================
  Files           459      459              
  Lines          8883     8891       +8     
============================================
+ Hits           6017     6022       +5     
- Misses         2866     2869       +3     
Flag Coverage Δ
8.5 67.73% <100.00%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
src/API/Baggage/Propagation/Parser.php 100.00% <100.00%> (ø)

... and 5 files with indirect coverage changes


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 29066b4...1a83cd8. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@bobstrecansky
bobstrecansky merged commit 73d88a0 into open-telemetry:main May 28, 2026
6 of 10 checks passed
@bobstrecansky

Copy link
Copy Markdown
Contributor

I had a couple of non-blockers, adding here in case you want to fast-follow @tonghuaroot:

  1. Boundary tests — tests/Unit/API/Baggage/Propagation/ParserTest.php tests fire well past the limits (1024 pairs, 500 pairs) but don't pin > vs >=. Add cases for exactly 8192 bytes (accept), 8193 bytes (reject), and 180 vs 181 entries to lock the off-by-one.

  2. Entry-counter intent comment — Parser.php:36$entries increments only on accepted pairs, not raw list-members. A future maintainer could move the increment and silently change semantics. One-line comment recommended.

  3. Silent drop observability — when the byte cap trips the entire header is discarded with no signal. Matches W3C "MAY truncate or discard" and sibling SDKs, but optional self-diagnostics emission would help debugging.

@tonghuaroot

Copy link
Copy Markdown
Contributor Author

Thanks for the thoughtful review @bobstrecansky! I've opened a fast-follow to pick up your suggestions: #1976.

  • Boundary tests — added four cases that pin the exact edge the existing tests skipped over: 8192 bytes accepted / 8193 rejected (the byte cap is inclusive, strlen() > MAX_BAGGAGE_BYTES), and 180 entries accepted / 181st dropped ($entries >= MAX_BAGGAGE_ENTRIES). They lock the off-by-one against the real implemented semantics.
  • Intent comment — added a one-liner noting that $entries counts only accepted pairs (incremented at the bottom of the loop), so a future change won't silently relocate the counter and alter the cap semantics.
  • Silent-drop observability — deferred by design. Discarding an over-cap header matches the W3C spec wording ("MAY truncate or discard") and the behaviour of the sibling SDKs, so I left self-diagnostics out for now; happy to revisit if you'd like it tracked separately.

phpunit --filter ParserTest is green (17 tests, 43 assertions).

bobstrecansky pushed a commit that referenced this pull request May 28, 2026
Follow-up to #1975. Pins the exact off-by-one behaviour of the W3C
Baggage caps that the existing tests (1024 pairs / 500 pairs) only
exercised well past the limits:

- byte cap is inclusive: 8192 bytes accepted, 8193 bytes rejected
- list-member cap: 180 entries accepted, 181st dropped

Also documents that $entries counts only ACCEPTED pairs (incremented at
the bottom of the loop), not raw comma-separated list-members, so a
future change does not silently move the counter and alter semantics.

Signed-off-by: tonghuaroot <[email protected]>
@Devansh1093 Devansh1093 mentioned this pull request Jun 2, 2026
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.

Baggage: extract has no size or entry-count cap (hardening)

2 participants