fix: scrub canonical Anthropic headers from 3P shim requests - #499
Conversation
The remaining blocker from PR Twigpine#268 was that canonical Anthropic headers such as `anthropic-version` and `anthropic-beta` could still ride through supported 3P paths even after the earlier x-anthropic/x-claude scrubber work. This tightens header filtering inside the shim itself so direct defaultHeaders, env-driven client setup, providerOverride routing, and per-request header injection all share the same scrubber. Constraint: Preserve non-Anthropic custom headers and provider auth while stripping only Anthropic/OpenClaude-internal headers from 3P requests Rejected: Rely on client.ts filtering alone | direct shim construction and per-request headers would still leave gaps Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep header scrubbing centralized in the shim so new call paths do not reopen 3P leakage bugs Tested: bun test src/services/api/openaiShim.test.ts src/services/api/client.test.ts src/utils/context.test.ts Tested: bun run test:provider Tested: bun run build && node dist/cli.mjs --version Not-tested: bun run typecheck (repository baseline currently fails in many unrelated files)
There was a problem hiding this comment.
Pull request overview
This PR closes a remaining header-leak gap in the OpenAI-compatible shim path by ensuring canonical Anthropic headers (e.g. anthropic-version, anthropic-beta) are scrubbed before third-party requests are sent.
Changes:
- Add centralized header filtering in
openaiShim.tsto drop canonicalanthropic-*and other Anthropic/Claude/auth headers. - Apply filtering to both shim
defaultHeadersand per-requestoptions.headers. - Add/extend tests covering direct shim usage and
getAnthropicClient()routing paths (includingANTHROPIC_CUSTOM_HEADERSandproviderOverride).
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
src/services/api/openaiShim.ts |
Introduces and applies a centralized header scrubber for OpenAI-compatible requests. |
src/services/api/openaiShim.test.ts |
Adds regression tests ensuring canonical Anthropic headers are removed from shim headers. |
src/services/api/client.test.ts |
Adds regression tests ensuring env-driven custom headers are scrubbed on shim/providerOverride requests. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| afterEach(() => { | ||
| ;(globalThis as Record<string, unknown>).MACRO = originalMacro | ||
| process.env.CLAUDE_CODE_USE_OPENAI = originalEnv.CLAUDE_CODE_USE_OPENAI | ||
| process.env.CLAUDE_CODE_USE_GEMINI = originalEnv.CLAUDE_CODE_USE_GEMINI | ||
| process.env.GEMINI_API_KEY = originalEnv.GEMINI_API_KEY |
There was a problem hiding this comment.
In this test teardown, assigning process.env.CLAUDE_CODE_USE_OPENAI = originalEnv... can leave the env var set to the literal string "undefined" when the original value was unset, which can leak state into later tests. Prefer deleting the key when the original value is undefined (e.g., via a small restoreEnv helper like in openaiShim.test.ts).
| process.env.OPENAI_MODEL = originalEnv.OPENAI_MODEL | ||
| process.env.ANTHROPIC_API_KEY = originalEnv.ANTHROPIC_API_KEY | ||
| process.env.ANTHROPIC_AUTH_TOKEN = originalEnv.ANTHROPIC_AUTH_TOKEN | ||
| process.env.ANTHROPIC_CUSTOM_HEADERS = originalEnv.ANTHROPIC_CUSTOM_HEADERS |
There was a problem hiding this comment.
Similarly, restoring ANTHROPIC_CUSTOM_HEADERS via direct assignment can set it to the string "undefined" when it was originally unset. Consider deleting the env var when the saved value is undefined to avoid cross-test contamination.
| process.env.ANTHROPIC_CUSTOM_HEADERS = originalEnv.ANTHROPIC_CUSTOM_HEADERS | |
| if (originalEnv.ANTHROPIC_CUSTOM_HEADERS === undefined) { | |
| delete process.env.ANTHROPIC_CUSTOM_HEADERS | |
| } else { | |
| process.env.ANTHROPIC_CUSTOM_HEADERS = originalEnv.ANTHROPIC_CUSTOM_HEADERS | |
| } |
The new header-leak regression tests in client.test.ts restored environment variables via direct assignment, which can leave literal "undefined" strings in process.env when the original value was unset. This switches the teardown over to the same restore helper pattern already used in openaiShim.test.ts. Constraint: Keep the fix limited to test hygiene without altering runtime behavior Rejected: Restore only the two env vars Copilot called out | using one helper for all test env restores is simpler and less error-prone Confidence: high Scope-risk: narrow Reversibility: clean Directive: Use restore helpers for env teardown in tests so unset values stay deleted instead of becoming the string "undefined" Tested: bun test src/services/api/client.test.ts src/services/api/openaiShim.test.ts src/utils/context.test.ts Not-tested: Full provider suite (unchanged runtime path)
gnanam1990
left a comment
There was a problem hiding this comment.
Thanks for the PR! Code looks good, scope is tight, and the fix addresses the root cause. Verified locally through the focused coverage and CI is green. LGTM.
Vasanthdev2004
left a comment
There was a problem hiding this comment.
Rechecked the latest head 5751123.
One inconsistency relative to the PR's own stated goal:
Codex transport path still leaks unfiltered per-request headers (Low)
In _doRequest (openaiShim.ts), the Codex path at line ~1010 passes raw options?.headers without filtering:
\\ s
defaultHeaders: {
...this.defaultHeaders, // filtered at construction time ✓
...(options?.headers ?? {}), // NOT filtered ✗
},
\\
Both the standard OpenAI path and the non-streaming OpenAI path now call filterAnthropicHeaders(options?.headers), but the Codex path was missed. A caller passing anthropic-version or x-anthropic-* headers via options.headers into a Codex request would bypass the scrub.
Fix: change the line to use filterAnthropicHeaders(options?.headers) to match the other paths.
Severity is low since the Codex transport is specialized and callers are internal, but it's an inconsistency the PR's stated goal should cover.
The filterAnthropicHeaders function itself is solid — correct lowercase matching, undefined handling, and comprehensive prefix coverage. Test coverage is thorough across all four entry points (defaultHeaders, ANTHROPIC_CUSTOM_HEADERS, providerOverride, per-request options), with the gap noted above.
|
please fix conflicts too @ibaaaaal |
Merged upstream/main into the PR branch to clear GitHub conflicts. The only manual conflict was in src/services/api/client.test.ts, where the branch's restoreEnv-based cleanup was kept and extended with upstream's GEMINI_AUTH_MODE restoration so the test cleanup stays robust across both workstreams. Constraint: GitHub reports this PR branch as conflicted against upstream main Rejected: Rebase before settling the reviewer concern | would mix history rewrite with ongoing verification work Confidence: high Scope-risk: moderate Directive: Re-run the focused shim/provider test slice after any later base sync because upstream changed provider-routing files in the merge range Tested: bun test src/services/api/openaiShim.test.ts src/services/api/client.test.ts src/services/api/codexShim.test.ts src/services/api/providerConfig.github.test.ts Tested: bun run build Not-tested: Full repository test suite after merge
…eaders A base-sync with upstream exposed a separate GitHub+Codex transport branch that still merged per-request headers raw before adding Copilot headers. This keeps the filter aligned across Codex-family paths and adds explicit regression tests for GitHub Codex routing, including providerOverride. Constraint: Must not push or modify GitHub state while validating the reviewer concern Rejected: Leave the GitHub Codex path unchanged | runtime repro showed anthropic-* headers still leaked after the upstream sync Confidence: high Scope-risk: narrow Directive: Keep header scrubbing consistent across every Codex-family transport branch when provider routing changes Tested: bun test src/services/api/openaiShim.test.ts Tested: bun test src/services/api/client.test.ts src/services/api/codexShim.test.ts src/services/api/providerConfig.github.test.ts Tested: bun run build Not-tested: Full repository test suite
e7e9e10 to
1fb88c8
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
src/services/api/openaiShim.ts:1067
_doRequest()treats anycodex_responsesrequest in GitHub mode as a GitHub Copilot responses request (isGithubWithCodexTransport), which means Codex-alias models (e.g.codexplan/codexspark) will bypassresolveCodexApiCredentials()and instead require a Copilot token.
To avoid breaking Codex aliases, tighten the GitHub branch condition (e.g. also require the Copilot endpoint type / baseUrl) or explicitly exclude Codex-alias routing when CLAUDE_CODE_USE_GITHUB is set.
const githubEndpointType = getGithubEndpointType(request.baseUrl)
const isGithubMode = isGithubModelsMode()
const isGithubWithCodexTransport = isGithubMode && request.transport === 'codex_responses'
const isGithubCopilotEndpoint = isGithubMode && githubEndpointType === 'copilot'
if (isGithubWithCodexTransport) {
const apiKey = this.providerOverride?.apiKey ?? process.env.OPENAI_API_KEY ?? ''
if (!apiKey) {
throw new Error(
'GitHub Copilot auth is required. Run /onboard-github to sign in.',
)
}
return performCodexRequest({
request,
credentials: {
apiKey,
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
PR #499 Review: Scrub canonical Anthropic headers from 3P shim requestsOverviewThis PR closes a header-leak gap where canonical What's good
Issues found1. (Medium) Codex transport path still leaks unfiltered At defaultHeaders: {
...this.defaultHeaders, // filtered at construction ✓
...(options?.headers ?? {}), // NOT filtered ✗
},The other three paths (streaming OpenAI at :1070, non-streaming OpenAI at :1102, and the main request at :1194) all use Fix: Change line 1010 to: ...filterAnthropicHeaders(options?.headers),Severity is low-to-medium — the Codex path callers are internal and the 2. (Low) Stripping 3. (Nit) Duplicate test boilerplate The 5 new tests have significant boilerplate (mock VerdictApprove with one requested change — fix the Codex path at line 1010 to use |
|
hey @Vasanthdev2004, just to clarify — 5751123 isn’t the latest head; it’s an earlier test-hygiene commit. the branch was force-pushed afterward, and the current head is 1fb88c8. |
gnanam1990
left a comment
There was a problem hiding this comment.
Thanks for the PR. I re-checked the current head and this now looks good to merge. The scope is tight, the fix addresses a real trust-boundary issue, and the remaining Codex-path inconsistency appears resolved on the latest head.
I verified locally with a real checkout of the PR plus focused coverage and build:
bun test src/services/api/openaiShim.test.ts src/services/api/client.test.ts src/utils/context.test.ts src/services/api/codexShim.test.ts src/services/api/providerConfig.github.test.ts✅bun run build✅
I’m comfortable with this as a narrow hardening fix for preventing canonical Anthropic headers from leaking into 3P shim requests. LGTM.
|
Rechecked the current head The specific inconsistency I called out on the older head looks resolved now: the Codex-family So at this point the remaining shape looks tight: narrow trust-boundary hardening, focused tests, and green checks. This now looks Approve-ready on the current head. |
Vasanthdev2004
left a comment
There was a problem hiding this comment.
Rechecked the current head 1fb88c81f11332c5a58306fc9d217e9646c39207.
Scope
This is a targeted re-review of the current head and the previously-raised Codex-path concern, not a full fresh re-review of every historical branch state.
Verdict
Approve-ready
What I rechecked on this head:
- the earlier missed Codex/GitHub Codex per-request header path is now using
filterAnthropicHeaders(options?.headers) - the central scrubber application is consistent across the relevant 3P shim request paths
- scope remains tight to the stated security hardening goal
- checks are green
Given that, I’m comfortable approving the current head.
auriti
left a comment
There was a problem hiding this comment.
Rechecked the current head.
The remaining Codex-path inconsistency that Vasanthdev2004 flagged now appears resolved: the Codex/GitHub Codex per-request path is using the same Anthropic-header scrubber as the other shim request paths, and the added regression coverage matches the stated trust-boundary goal.
Scope stays tight and the hardening is pointed at a real 3P leak path. I’m comfortable with this as merge-ready.
…e#499) * Stop canonical Anthropic headers from leaking into 3P shim requests The remaining blocker from PR Twigpine#268 was that canonical Anthropic headers such as `anthropic-version` and `anthropic-beta` could still ride through supported 3P paths even after the earlier x-anthropic/x-claude scrubber work. This tightens header filtering inside the shim itself so direct defaultHeaders, env-driven client setup, providerOverride routing, and per-request header injection all share the same scrubber. Constraint: Preserve non-Anthropic custom headers and provider auth while stripping only Anthropic/OpenClaude-internal headers from 3P requests Rejected: Rely on client.ts filtering alone | direct shim construction and per-request headers would still leave gaps Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep header scrubbing centralized in the shim so new call paths do not reopen 3P leakage bugs Tested: bun test src/services/api/openaiShim.test.ts src/services/api/client.test.ts src/utils/context.test.ts Tested: bun run test:provider Tested: bun run build && node dist/cli.mjs --version Not-tested: bun run typecheck (repository baseline currently fails in many unrelated files) * Keep OpenAI client tests from restoring undefined env as strings The new header-leak regression tests in client.test.ts restored environment variables via direct assignment, which can leave literal "undefined" strings in process.env when the original value was unset. This switches the teardown over to the same restore helper pattern already used in openaiShim.test.ts. Constraint: Keep the fix limited to test hygiene without altering runtime behavior Rejected: Restore only the two env vars Copilot called out | using one helper for all test env restores is simpler and less error-prone Confidence: high Scope-risk: narrow Reversibility: clean Directive: Use restore helpers for env teardown in tests so unset values stay deleted instead of becoming the string "undefined" Tested: bun test src/services/api/client.test.ts src/services/api/openaiShim.test.ts src/utils/context.test.ts Not-tested: Full provider suite (unchanged runtime path) * Prevent GitHub Codex requests from forwarding unsanitized Anthropic headers A base-sync with upstream exposed a separate GitHub+Codex transport branch that still merged per-request headers raw before adding Copilot headers. This keeps the filter aligned across Codex-family paths and adds explicit regression tests for GitHub Codex routing, including providerOverride. Constraint: Must not push or modify GitHub state while validating the reviewer concern Rejected: Leave the GitHub Codex path unchanged | runtime repro showed anthropic-* headers still leaked after the upstream sync Confidence: high Scope-risk: narrow Directive: Keep header scrubbing consistent across every Codex-family transport branch when provider routing changes Tested: bun test src/services/api/openaiShim.test.ts Tested: bun test src/services/api/client.test.ts src/services/api/codexShim.test.ts src/services/api/providerConfig.github.test.ts Tested: bun run build Not-tested: Full repository test suite
…e#499) * Stop canonical Anthropic headers from leaking into 3P shim requests The remaining blocker from PR Twigpine#268 was that canonical Anthropic headers such as `anthropic-version` and `anthropic-beta` could still ride through supported 3P paths even after the earlier x-anthropic/x-claude scrubber work. This tightens header filtering inside the shim itself so direct defaultHeaders, env-driven client setup, providerOverride routing, and per-request header injection all share the same scrubber. Constraint: Preserve non-Anthropic custom headers and provider auth while stripping only Anthropic/OpenClaude-internal headers from 3P requests Rejected: Rely on client.ts filtering alone | direct shim construction and per-request headers would still leave gaps Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep header scrubbing centralized in the shim so new call paths do not reopen 3P leakage bugs Tested: bun test src/services/api/openaiShim.test.ts src/services/api/client.test.ts src/utils/context.test.ts Tested: bun run test:provider Tested: bun run build && node dist/cli.mjs --version Not-tested: bun run typecheck (repository baseline currently fails in many unrelated files) * Keep OpenAI client tests from restoring undefined env as strings The new header-leak regression tests in client.test.ts restored environment variables via direct assignment, which can leave literal "undefined" strings in process.env when the original value was unset. This switches the teardown over to the same restore helper pattern already used in openaiShim.test.ts. Constraint: Keep the fix limited to test hygiene without altering runtime behavior Rejected: Restore only the two env vars Copilot called out | using one helper for all test env restores is simpler and less error-prone Confidence: high Scope-risk: narrow Reversibility: clean Directive: Use restore helpers for env teardown in tests so unset values stay deleted instead of becoming the string "undefined" Tested: bun test src/services/api/client.test.ts src/services/api/openaiShim.test.ts src/utils/context.test.ts Not-tested: Full provider suite (unchanged runtime path) * Prevent GitHub Codex requests from forwarding unsanitized Anthropic headers A base-sync with upstream exposed a separate GitHub+Codex transport branch that still merged per-request headers raw before adding Copilot headers. This keeps the filter aligned across Codex-family paths and adds explicit regression tests for GitHub Codex routing, including providerOverride. Constraint: Must not push or modify GitHub state while validating the reviewer concern Rejected: Leave the GitHub Codex path unchanged | runtime repro showed anthropic-* headers still leaked after the upstream sync Confidence: high Scope-risk: narrow Directive: Keep header scrubbing consistent across every Codex-family transport branch when provider routing changes Tested: bun test src/services/api/openaiShim.test.ts Tested: bun test src/services/api/client.test.ts src/services/api/codexShim.test.ts src/services/api/providerConfig.github.test.ts Tested: bun run build Not-tested: Full repository test suite
…e#499) * Stop canonical Anthropic headers from leaking into 3P shim requests The remaining blocker from PR Twigpine#268 was that canonical Anthropic headers such as `anthropic-version` and `anthropic-beta` could still ride through supported 3P paths even after the earlier x-anthropic/x-claude scrubber work. This tightens header filtering inside the shim itself so direct defaultHeaders, env-driven client setup, providerOverride routing, and per-request header injection all share the same scrubber. Constraint: Preserve non-Anthropic custom headers and provider auth while stripping only Anthropic/OpenClaude-internal headers from 3P requests Rejected: Rely on client.ts filtering alone | direct shim construction and per-request headers would still leave gaps Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep header scrubbing centralized in the shim so new call paths do not reopen 3P leakage bugs Tested: bun test src/services/api/openaiShim.test.ts src/services/api/client.test.ts src/utils/context.test.ts Tested: bun run test:provider Tested: bun run build && node dist/cli.mjs --version Not-tested: bun run typecheck (repository baseline currently fails in many unrelated files) * Keep OpenAI client tests from restoring undefined env as strings The new header-leak regression tests in client.test.ts restored environment variables via direct assignment, which can leave literal "undefined" strings in process.env when the original value was unset. This switches the teardown over to the same restore helper pattern already used in openaiShim.test.ts. Constraint: Keep the fix limited to test hygiene without altering runtime behavior Rejected: Restore only the two env vars Copilot called out | using one helper for all test env restores is simpler and less error-prone Confidence: high Scope-risk: narrow Reversibility: clean Directive: Use restore helpers for env teardown in tests so unset values stay deleted instead of becoming the string "undefined" Tested: bun test src/services/api/client.test.ts src/services/api/openaiShim.test.ts src/utils/context.test.ts Not-tested: Full provider suite (unchanged runtime path) * Prevent GitHub Codex requests from forwarding unsanitized Anthropic headers A base-sync with upstream exposed a separate GitHub+Codex transport branch that still merged per-request headers raw before adding Copilot headers. This keeps the filter aligned across Codex-family paths and adds explicit regression tests for GitHub Codex routing, including providerOverride. Constraint: Must not push or modify GitHub state while validating the reviewer concern Rejected: Leave the GitHub Codex path unchanged | runtime repro showed anthropic-* headers still leaked after the upstream sync Confidence: high Scope-risk: narrow Directive: Keep header scrubbing consistent across every Codex-family transport branch when provider routing changes Tested: bun test src/services/api/openaiShim.test.ts Tested: bun test src/services/api/client.test.ts src/services/api/codexShim.test.ts src/services/api/providerConfig.github.test.ts Tested: bun run build Not-tested: Full repository test suite
…e#499) * Stop canonical Anthropic headers from leaking into 3P shim requests The remaining blocker from PR Twigpine#268 was that canonical Anthropic headers such as `anthropic-version` and `anthropic-beta` could still ride through supported 3P paths even after the earlier x-anthropic/x-claude scrubber work. This tightens header filtering inside the shim itself so direct defaultHeaders, env-driven client setup, providerOverride routing, and per-request header injection all share the same scrubber. Constraint: Preserve non-Anthropic custom headers and provider auth while stripping only Anthropic/OpenClaude-internal headers from 3P requests Rejected: Rely on client.ts filtering alone | direct shim construction and per-request headers would still leave gaps Confidence: high Scope-risk: narrow Reversibility: clean Directive: Keep header scrubbing centralized in the shim so new call paths do not reopen 3P leakage bugs Tested: bun test src/services/api/openaiShim.test.ts src/services/api/client.test.ts src/utils/context.test.ts Tested: bun run test:provider Tested: bun run build && node dist/cli.mjs --version Not-tested: bun run typecheck (repository baseline currently fails in many unrelated files) * Keep OpenAI client tests from restoring undefined env as strings The new header-leak regression tests in client.test.ts restored environment variables via direct assignment, which can leave literal "undefined" strings in process.env when the original value was unset. This switches the teardown over to the same restore helper pattern already used in openaiShim.test.ts. Constraint: Keep the fix limited to test hygiene without altering runtime behavior Rejected: Restore only the two env vars Copilot called out | using one helper for all test env restores is simpler and less error-prone Confidence: high Scope-risk: narrow Reversibility: clean Directive: Use restore helpers for env teardown in tests so unset values stay deleted instead of becoming the string "undefined" Tested: bun test src/services/api/client.test.ts src/services/api/openaiShim.test.ts src/utils/context.test.ts Not-tested: Full provider suite (unchanged runtime path) * Prevent GitHub Codex requests from forwarding unsanitized Anthropic headers A base-sync with upstream exposed a separate GitHub+Codex transport branch that still merged per-request headers raw before adding Copilot headers. This keeps the filter aligned across Codex-family paths and adds explicit regression tests for GitHub Codex routing, including providerOverride. Constraint: Must not push or modify GitHub state while validating the reviewer concern Rejected: Leave the GitHub Codex path unchanged | runtime repro showed anthropic-* headers still leaked after the upstream sync Confidence: high Scope-risk: narrow Directive: Keep header scrubbing consistent across every Codex-family transport branch when provider routing changes Tested: bun test src/services/api/openaiShim.test.ts Tested: bun test src/services/api/client.test.ts src/services/api/codexShim.test.ts src/services/api/providerConfig.github.test.ts Tested: bun run build Not-tested: Full repository test suite
Summary
Follow-up to #268 and the remaining header-leak blocker from #267.
The current scrubber already removes
x-anthropic-*,x-claude-*, and auth headers, but canonical Anthropic headers likeanthropic-versionandanthropic-betacan still reach third-party OpenAI-compatible requests.This patch closes that gap.
What changed
anthropic-*headersopenaiShim.tsdefaultHeadersANTHROPIC_CUSTOM_HEADERSproviderOverrideoptions.headersTests
bun test src/services/api/openaiShim.test.ts src/services/api/client.test.ts src/utils/context.test.tsbun run test:providerbun run build && node dist/cli.mjs --version