Repository navigation
fix(sse): strip stale content-encoding/length/transfer-encoding from upstream responses - #2291
Merged
diegosouzapw merged 5 commits intoMay 16, 2026
Conversation
…tion Embed Gemini, Antigravity and Windsurf public OAuth/Firebase identifiers (extracted from upstream CLI binaries) through a XOR-masked byte sequence in open-sse/utils/publicCreds.ts instead of source literals, so pattern scanners (GitHub Secret Scanning, Semgrep) stop raising false positives on every release. decodePublicCred passes raw values through unchanged for users who already have plaintext in their .env (no migration needed). - New utils/publicCreds.ts with decode/encode + tests - Replace 6 hardcoded Google client_id/secret in oauth.ts + providerRegistry - Drop literals from .env.example (comment-only documentation) - Sanitize error messages inside buildErrorBody so every caller (incl. createErrorResult) is covered; cursor.ts now reuses the shared helper - Cover the new helpers with unit tests (publicCreds + error sanitization) Resolves the open code-scanning js/stack-trace-exposure findings and the secret-scanning Google API Key alert without breaking existing setups. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…terns Fixes the 4 fixable alerts opened in the recent scan and adds enforceable guardrails so future development follows the same pattern. Code fixes: - src/mitm/cert/install.ts: pass certPath/certName/action via exec()'s env option instead of string-interpolating them into the bash script (CodeQL js/shell-command-injection-from-environment cheaper-inference#225) - scripts/docs/{gen-provider-reference,add-frontmatter,fix-internal-links}: escape backslash before other regex/markdown metacharacters (CodeQL js/incomplete-sanitization cheaper-inference#227, cheaper-inference#228, cheaper-inference#229) Documentation (mandatory patterns): - docs/security/PUBLIC_CREDS.md — embedding public upstream OAuth/Firebase identifiers via resolvePublicCred(); never as string literals - docs/security/ERROR_SANITIZATION.md — routing every error response through sanitizeErrorMessage()/buildErrorBody(); never raw err.stack/err.message - CLAUDE.md: 4 new Hard Rules (cheaper-inference#11-cheaper-inference#14) + Security section + scenario notes - AGENTS.md, CONTRIBUTING.md: cross-reference the two new docs - SECURITY.md: extended Hard Security Rules with the new mandatory patterns - docs/README.md: index entries pointing to the two new docs Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The PATH_REGEX introduced in 1a39c31 used a greedy character class ([\w\-./\\]+) that can backtrack quadratically on inputs like "///////..." — flagged by CodeQL js/polynomial-redos (cheaper-inference#232). Replace it with simple whitespace tokenization + a length-bounded prefix-check helper. Same observable behaviour (paths replaced with <path>), but linear time regardless of input shape. Adds a 4096-char input cap as defence in depth. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…precedent CodeQL js/stack-trace-exposure does not recognize sanitizers reached through a custom helper indirection — flagging callsites like open-sse/utils/error.ts::errorResponse and open-sse/executors/cursor.ts::buildErrorResponse even though both route through sanitizeErrorMessage(). Record the dismissal precedent (alerts cheaper-inference#224 and cheaper-inference#231, May 2026): - Add a "Known CodeQL limitation" section in docs/security/ERROR_SANITIZATION.md - Extend CLAUDE.md Hard Rule cheaper-inference#14 with the precedent Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…upstream responses `fetch()` always transparently decompresses the upstream body before exposing it via `.text()` or the stream reader, so forwarding the upstream `content-encoding` header (e.g. `gzip`) to the downstream OpenAI/Anthropic-compatible client makes the client attempt to gunzip plain text and fail with `ZlibError: incorrect header check`. Similarly, `content-length` becomes stale once we transform the response body (non-stream path repacks via `new Response()`; stream path re-streams through our SSE heartbeat / shape transforms), and `transfer-encoding` is owned by the Node/Next runtime, not us. This patch adds `open-sse/utils/upstreamResponseHeaders.ts` exporting: - `stripStaleEncodingHeaders(input: Headers)` — returns a new `Headers` with content-encoding, content-length, transfer-encoding removed (case-insensitive, does not mutate input). - `filterUpstreamResponseHeaderEntries(entries, extraToStrip)` — same semantics for the entries-array path used by the streaming response builder, plus user-supplied additional names (case-insensitive). - `STRIP_UPSTREAM_HEADER_NAMES` — the canonical set, exported for tests. `open-sse/handlers/chatCore.ts` now uses these helpers in two places: 1. Non-stream path (~L3211): replaces `new Headers(rawResult.response.headers)` with `stripStaleEncodingHeaders(...)` so the repacked Response below doesn't claim a stale encoding/length. 2. Stream path (~L4371): replaces an inline IIFE+filter that only removed `content-type` with `filterUpstreamResponseHeaderEntries(..., ["content-type"])` so the stale encoding/length are also stripped before we set our own `text/event-stream` content-type. Both call sites carry a comment explaining the underlying fetch decompression behavior. Tests ----- New `tests/unit/upstream-response-headers-strip.test.ts` adds 10 cases covering: lowercase strip, mixed-case strip, input non-mutation, empty input, default-set filter, case-insensitive extraToStrip, empty extraToStrip preservation, empty entries, mixed-case default names, and canonical set identity. Verified locally ---------------- - `npx eslint open-sse/utils/upstreamResponseHeaders.ts open-sse/handlers/chatCore.ts tests/unit/upstream-response-headers-strip.test.ts` — clean. - `npm run typecheck:core` — clean. - `node --import tsx/esm --test tests/unit/upstream-response-headers-strip.test.ts tests/unit/upstream-headers-sanitize.test.ts tests/unit/chatcore-sanitization.test.ts tests/unit/chat-route-edge-cases.test.ts tests/unit/chatcore-translation-paths.test.ts tests/unit/chatcore-compression-integration.test.ts` — 95/95 pass. - Full `npm run test:unit` / `test:coverage` deferred to upstream CI (122 test files, exceeds local timeout window).
Contributor
There was a problem hiding this comment.
Code Review
This pull request introduces a utility to strip stale encoding and length headers from upstream provider responses to prevent client-side decompression errors. It adds a new utility file open-sse/utils/upstreamResponseHeaders.ts containing stripStaleEncodingHeaders and filterUpstreamResponseHeaderEntries, integrates these into open-sse/handlers/chatCore.ts, and adds corresponding unit tests. I have no feedback to provide.
diegosouzapw
merged commit May 16, 2026
acc9a87
into
cheaper-inference:release/v3.8.0
3 checks passed
diegosouzapw
added a commit
that referenced
this pull request
May 16, 2026
…, #2291, #2294, #2295, #2299 - Add 8 missing contributor PR entries to [Unreleased] section - Add 3 new contributors to the community table: @thepigdestroyer (2 PRs), @josephvoxone (1 PR), @mrmm (1 PR) - Update counts: @oyi77 12→14, @backryun 8→9, @ddarkr 3→4, @hartmark 2→4 - Compensates for cherry-picked PRs that couldn't be properly merged via GitHub (branch deleted or fork restriction)
This was referenced May 16, 2026
Merged
This was referenced Jul 23, 2026
HouMinXi
pushed a commit
to HouMinXi/OmniRoute
that referenced
this pull request
Aug 2, 2026
…upstream responses (cheaper-inference#2291) Authored-by: Paijo <oyi77@users.noreply.github.com>
HouMinXi
pushed a commit
to HouMinXi/OmniRoute
that referenced
this pull request
Aug 2, 2026
…heaper-inference#2288, cheaper-inference#2289, cheaper-inference#2290, cheaper-inference#2291, cheaper-inference#2294, cheaper-inference#2295, cheaper-inference#2299 - Add 8 missing contributor PR entries to [Unreleased] section - Add 3 new contributors to the community table: @thepigdestroyer (2 PRs), @josephvoxone (1 PR), @mrmm (1 PR) - Update counts: @oyi77 12→14, @backryun 8→9, @ddarkr 3→4, @hartmark 2→4 - Compensates for cherry-picked PRs that couldn't be properly merged via GitHub (branch deleted or fork restriction)
Poid-ZA
pushed a commit
to Poid-ZA/OmniRoute
that referenced
this pull request
Aug 5, 2026
…upstream responses (cheaper-inference#2291) Authored-by: Paijo <oyi77@users.noreply.github.com>
Poid-ZA
pushed a commit
to Poid-ZA/OmniRoute
that referenced
this pull request
Aug 5, 2026
…heaper-inference#2288, cheaper-inference#2289, cheaper-inference#2290, cheaper-inference#2291, cheaper-inference#2294, cheaper-inference#2295, cheaper-inference#2299 - Add 8 missing contributor PR entries to [Unreleased] section - Add 3 new contributors to the community table: @thepigdestroyer (2 PRs), @josephvoxone (1 PR), @mrmm (1 PR) - Update counts: @oyi77 12→14, @backryun 8→9, @ddarkr 3→4, @hartmark 2→4 - Compensates for cherry-picked PRs that couldn't be properly merged via GitHub (branch deleted or fork restriction)
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…upstream responses (cheaper-inference#2291) Authored-by: Paijo <oyi77@users.noreply.github.com>
muhamadgalihsaputra
pushed a commit
to niyatna/NiyatnaRoute
that referenced
this pull request
Sep 27, 2026
…heaper-inference#2288, cheaper-inference#2289, cheaper-inference#2290, cheaper-inference#2291, cheaper-inference#2294, cheaper-inference#2295, cheaper-inference#2299 - Add 8 missing contributor PR entries to [Unreleased] section - Add 3 new contributors to the community table: @thepigdestroyer (2 PRs), @josephvoxone (1 PR), @mrmm (1 PR) - Update counts: @oyi77 12→14, @backryun 8→9, @ddarkr 3→4, @hartmark 2→4 - Compensates for cherry-picked PRs that couldn't be properly merged via GitHub (branch deleted or fork restriction)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
When the upstream provider returns a gzip-encoded response, the SSE bridge currently forwards
content-encoding: gzip(and stalecontent-length/transfer-encoding) verbatim to the downstream OpenAI/Anthropic-compatible client. Butfetch()has already transparently decompressed the body before we read it via.text()or the stream reader, so the client receives plain text labelled as gzip and explodes withZlibError: incorrect header check.This PR adds a small helper module and uses it at the two response-construction sites in
open-sse/handlers/chatCore.ts(non-stream + stream).Observed symptom that motivated this: real-world clients (e.g. the Anthropic SDK and some OpenAI proxies) fail with
ZlibErrormid-stream when the upstream happened to negotiate gzip. The fix is independent of which upstream provider was contacted — it's purely a "don't lie about how the body is encoded after we touched it" hygiene fix.Changes
open-sse/utils/upstreamResponseHeaders.tsexporting:stripStaleEncodingHeaders(input: Headers): Headers— case-insensitive, non-mutating; dropscontent-encoding,content-length,transfer-encoding.filterUpstreamResponseHeaderEntries(entries, extraToStrip)— same semantics for the entries-array path used by the streaming response builder, plus user-supplied extra names (case-insensitive).STRIP_UPSTREAM_HEADER_NAMES— the canonical set, exported for tests.open-sse/handlers/chatCore.ts— two call sites refactored:new Headers(rawResult.response.headers)→stripStaleEncodingHeaders(rawResult.response.headers)(so the repackednew Response(...)below doesn't claim a stale encoding/length).IIFE + .filter()that only strippedcontent-typeis replaced withfilterUpstreamResponseHeaderEntries(providerResponse.headers.entries(), ["content-type"])so the stale encoding/length get stripped too before we set our ownContent-Type: text/event-stream.fetch()decompression behavior.Related Issues
Validation
npm run lint(scoped:npx eslinton the three touched files — clean)npm run test:unit— 122 test files, exceeds local timeout window; deferring to upstream CInpm run test:coverage— same; deferring to upstream CInpm run typecheck:core— cleannode --import tsx/esm --test tests/unit/upstream-response-headers-strip.test.ts tests/unit/upstream-headers-sanitize.test.ts tests/unit/chatcore-sanitization.test.ts tests/unit/chat-route-edge-cases.test.ts tests/unit/chatcore-translation-paths.test.ts tests/unit/chatcore-compression-integration.test.ts— 95/95 pass>= 60%for statements, lines, functions, and branches — full coverage run deferred to upstream CI; the change is small (one new module + 2 call sites) and the new test exercises every public export.Tests Added Or Updated
tests/unit/upstream-response-headers-strip.test.ts(10 cases):stripStaleEncodingHeaders— removes lowercase / removes mixed-case / does not mutate input / handles empty input.filterUpstreamResponseHeaderEntries— strips default set / case-insensitiveextraToStrip/ emptyextraToStrippreserves headers / empty input returns empty array / mixed-case default names.STRIP_UPSTREAM_HEADER_NAMES— canonical set identity.No existing test was modified.
Coverage Notes
open-sse/(the new util module + 2 call sites inchatCore.ts).chatCore.tsexchange one stale-headers expression for one strip-headers expression — coverage of the surrounding non-stream / stream branches is unchanged (still exercised bytests/unit/chat-route-edge-cases.test.ts,tests/unit/chatcore-translation-paths.test.ts,tests/unit/chatcore-compression-integration.test.ts, all passing locally).Reviewer Notes
open-sse/.Content-Encoding(Anthropic) andcontent-encoding(some OpenAI compatibles) in practice — the new test pins both.lint-staged,check-docs-sync,check:any-budget:t11,check-env-doc-sync, i18n drift/coverage warn-only) and were green at commit time.