Skip to content

Strip leading UTF-8 BOM before filtering list responses - #6304

Open
Yanhaoxi wants to merge 2 commits into
stacklok:mainfrom
Yanhaoxi:fix/bom-json-response-filter-bypass
Open

Strip leading UTF-8 BOM before filtering list responses#6304
Yanhaoxi wants to merge 2 commits into
stacklok:mainfrom
Yanhaoxi:fix/bom-json-response-filter-bypass

Conversation

@Yanhaoxi

Copy link
Copy Markdown
Contributor

Summary

A client strips a leading UTF-8 BOM per the WHATWG UTF-8 decode algorithm before parsing, but the response filters decoded and sniffed the raw bytes. Go's encoding/json treats EF BB BF as a syntax error (not whitespace), so a BOM-prefixed list response failed every decode and smuggled-result sniff and passed through unfiltered — leaking tools/prompts/resources that the Cedar policy (authz) or tool filter (mcp) was supposed to remove. This is the same #5257-class leak the SSE processing path was already hardened against; the JSON decode path, both FlushAndFilter default-branch sniffs, and the tool filter's unrecognized-media-type sniff were not.

WHAT changed:

  • Strip one leading BOM in FlushAndFilter before the media-type dispatch (pkg/authz/response_filter.go), covering the application/json decode path plus the JSON and SSE smuggled-result sniffs in the default branch.
  • Strip one leading BOM at the top of processUnrecognizedMimeType (pkg/mcp/tool_filter.go), covering both its JSON sniff and sniffSSEToolsList.
  • Add regression tests mirroring the existing SSE BOM test for each fixed route.

Fixes #6301

Type of change

  • Bug fix
  • New feature
  • Refactoring (no behavior change)
  • Dependency update
  • Documentation
  • Other (describe):

Test plan

  • Unit tests (task test)
  • E2E tests (task test-e2e)
  • Linting (task lint-fix)
  • Manual testing (describe below)

Run: go test ./pkg/authz/... ./pkg/mcp/.... New tests:
TestResponseFilteringWriter_JSON_LeadingBOMBypass (JSON path + JSON sniff), TestResponseFilteringWriter_Sniff_SSE_LeadingBOMBypass (SSE sniff), TestNewListToolsMappingMiddleware_BOMCannotSmuggleToolsList (tool filter, JSON + SSE). Verified the new tests FAIL without the source fix and PASS with it; full pkg/authz/... and pkg/mcp/... suites pass.

Lint: task lint reports only 3 pre-existing gci issues in files this PR does not touch (pkg/authz/annotation_cache.go, pkg/authz/authorizers.go, pkg/authz/config.go), confirmed present on clean main; none of this PR's 4 files are flagged. go vet clean, gofmt clean.

API Compatibility

  • This PR does not break the v1beta1 API.

Changes

File Change
pkg/authz/response_filter.go Strip leading BOM in FlushAndFilter before media-type dispatch
pkg/authz/response_filter_test.go Add JSON-path and SSE-sniff BOM regression tests
pkg/mcp/tool_filter.go Strip leading BOM in processUnrecognizedMimeType
pkg/mcp/tool_filter_test.go Add tool-filter BOM regression tests (JSON + SSE)

Does this introduce a user-facing change?

Yes: a misbehaving/untrusted upstream that prepends a UTF-8 BOM to a list response can no longer bypass list filtering; the denied items are filtered as before.

Implementation plan

Approved implementation plan

Four code points share one root cause: no leading-BOM strip before decode/sniff.

  • authz (3 points in one call chain): processJSONResponse decode + fail-closed check, JSON sniff (carriesResult), SSE sniff (sseCarriesResult). Fixed with one bytes.TrimPrefix in FlushAndFilter before the media-type switch.
  • mcp (1 point, separate component): processUnrecognizedMimeType JSON sniff + sniffSSEToolsList. Fixed with one bytes.TrimPrefix at the function entry (covers both sniffs).
  • Regression tests mirror TestResponseFilteringWriter_SSE_LeadingBOMBypass per route.

Special notes for reviewers

  • The existing SSE-processing BOM strip (processSSEResponse) is retained; bytes.TrimPrefix is idempotent so the redundant strip is harmless.
  • The unrecognized-media-type sniff branch is only reached when carriesResult/sseCarriesResult would previously have returned false on the BOM — the fix restores the fail-closed behavior those branches were built for (authz: SSE response filter leaks unfiltered tools/list on undecodable / non-Response data lines (bypasses cedar) #5257).
  • Known limitation unchanged: the tool filter's sniff has no "incomplete, wait for more" notion (documented in processUnrecognizedMimeType) — the BOM fix does not alter that.

A client strips a leading BOM per the WHATWG UTF-8 decode algorithm before parsing, but the response filter decoded and sniffed the raw bytes. Go's encoding/json treats EF BB BF as a syntax error, so a BOM-prefixed list response failed every decode and smuggled-result sniff and passed through unfiltered, leaking policy-denied tools/prompts/resources. The SSE processing path already stripped the BOM; the JSON decode path, the JSON/SSE sniffs in FlushAndFilter, and the tool filter's unrecognized-media-type sniff did not.

Strip the BOM once in FlushAndFilter before the media-type dispatch (covering the JSON path and both default-branch sniffs) and once at the top of processUnrecognizedMimeType in the tool filter. Add regression tests mirroring the existing SSE BOM test.
@codecov

codecov Bot commented Aug 13, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 72.87%. Comparing base (7854115) to head (683d102).

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #6304      +/-   ##
==========================================
+ Coverage   72.82%   72.87%   +0.05%     
==========================================
  Files         742      742              
  Lines       77762    77764       +2     
==========================================
+ Hits        56632    56673      +41     
+ Misses      17155    17100      -55     
- Partials     3975     3991      +16     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

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

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.

BOM-prefixed list responses bypass authz response filtering

1 participant