Skip to content

fix: strip Anthropic-specific fingerprint headers (anthropic-dangerou…#2228

Open
Sclock wants to merge 1 commit intorouter-for-me:mainfrom
Sclock:main
Open

fix: strip Anthropic-specific fingerprint headers (anthropic-dangerou…#2228
Sclock wants to merge 1 commit intorouter-for-me:mainfrom
Sclock:main

Conversation

@Sclock
Copy link

@Sclock Sclock commented Mar 19, 2026

Problem Description

The CLIProxyAPI gateway unconditionally forwards Anthropic-specific fingerprint headers to all upstream providers, including third-party ones like api.xheai.cc. This causes those upstreams to hang or return 400 Improperly formed request, while the same request body sent directly to the upstream (without these headers) succeeds with 200 OK.

Failing request to gateway (POST /v1/messages?beta=true, elapsed 1891ms → 400):

anthropic-beta: claude-code-20250219,interleaved-thinking-2025-05-14,prompt-caching-scope-2026-01-05,effort-2025-11-24 anthropic-dangerous-direct-browser-access: true x-app: cli model: claude-opus-4-6 

Gateway response:

{ "error": { "message": "Upstream bad request: Improperly formed request.", "type": "invalid_request_error" } }

Direct upstream request (same body, succeeded with 200):

Authorization: Bearer sk-sfAb5*** Content-Type: application/json anthropic-version: 2023-06-01 # no anthropic-beta, no anthropic-dangerous-direct-browser-access, no x-app 

Any one of the following headers is sufficient to trigger the issue:

Header Value Result
anthropic-beta contains claude-code-20250219 + model claude-opus-4-6 400 / hang
anthropic-dangerous-direct-browser-access true 400 / hang
x-app cli 400 / hang

Root Cause Analysis (RCA)

Location: internal/runtime/executor/claude_executor.go, function applyClaudeHeaders()

The function already computes isAnthropicBase to distinguish api.anthropic.com from other upstreams, but only uses it for auth header selection. The three Anthropic-specific fingerprint headers are set unconditionally:

// internal/runtime/executor/claude_executor.go — BEFORE FIX isAnthropicBase := r.URL != nil && strings.EqualFold(r.URL.Scheme, "https") && strings.EqualFold(r.URL.Host, "api.anthropic.com") // computed but only used for auth // baseBetas hardcodes claude-code-20250219 regardless of upstream baseBetas := "claude-code-20250219,oauth-2025-04-20,..." // ... r.Header.Set("Anthropic-Beta", baseBetas) // always set misc.EnsureHeader(..., "Anthropic-Dangerous-Direct-Browser-Access", "true") // always set misc.EnsureHeader(..., "X-App", "cli") // always set

The existing configuration system (ClaudeHeaderDefaults, per-key headers map) cannot work around this: custom headers can only add or overwrite values, not conditionally suppress headers that are hardcoded earlier in the same function.

Fix

Reuse the existing isAnthropicBase flag to gate the three problematic headers.

// internal/runtime/executor/claude_executor.go — AFTER FIX // Remove claude-code-20250219 for non-Anthropic upstreams: third-party providers // (e.g. api.xheai.cc) do not support this beta feature and will hang or return 400 // when it is present. The feature is only valid on api.anthropic.com. if !isAnthropicBase { baseBetas = removeBetaFeature(baseBetas, "claude-code-20250219") } // ... r.Header.Set("Anthropic-Beta", baseBetas) misc.EnsureHeader(r.Header, ginHeaders, "Anthropic-Version", "2023-06-01") // Only forward Anthropic-specific fingerprint headers to api.anthropic.com. // Third-party upstreams (e.g. api.xheai.cc) reject these headers with 400 or hang. if isAnthropicBase { misc.EnsureHeader(r.Header, ginHeaders, "Anthropic-Dangerous-Direct-Browser-Access", "true") misc.EnsureHeader(r.Header, ginHeaders, "X-App", "cli") }

New helper added in the same file:

func removeBetaFeature(header, featureToRemove string) string { parts := strings.Split(header, ",") filtered := parts[:0] for _, p := range parts { if strings.TrimSpace(p) != featureToRemove { filtered = append(filtered, p) } } return strings.Join(filtered, ",") }

The fix applies to all three call sites of applyClaudeHeadersExecute(), ExecuteStream(), and CountTokens() — in a single change.

Alternative considered: filtering at the handler or middleware layer — rejected because it would require duplicating logic across multiple paths, while applyClaudeHeaders is the single shared call site.

Test Verification

New tests in internal/runtime/executor/claude_executor_test.go:

// TestApplyClaudeHeaders_NonAnthropicUpstream verifies that Anthropic-specific // fingerprint headers are NOT forwarded when the upstream is not api.anthropic.com. func TestApplyClaudeHeaders_NonAnthropicUpstream(t *testing.T) { req, _ := http.NewRequest(http.MethodPost, "https://api.xheai.cc/v1/messages?beta=true", nil) auth := &cliproxyauth.Auth{ Attributes: map[string]string{ "api_key": "sk-test-key", "base_url": "https://api.xheai.cc", }, } applyClaudeHeaders(req, auth, "sk-test-key", false, nil, nil) if got := req.Header.Get("Anthropic-Dangerous-Direct-Browser-Access"); got != "" { t.Errorf("Anthropic-Dangerous-Direct-Browser-Access = %q, want empty", got) } if got := req.Header.Get("X-App"); got != "" { t.Errorf("X-App = %q, want empty", got) } if strings.Contains(req.Header.Get("Anthropic-Beta"), "claude-code-20250219") { t.Errorf("Anthropic-Beta contains claude-code-20250219: %q", req.Header.Get("Anthropic-Beta")) } }

Before fix (RED):

=== RUN TestApplyClaudeHeaders_NonAnthropicUpstream claude_executor_test.go:1085: Anthropic-Dangerous-Direct-Browser-Access = "true", want empty for non-Anthropic upstream claude_executor_test.go:1090: X-App = "cli", want empty for non-Anthropic upstream claude_executor_test.go:1096: Anthropic-Beta contains claude-code-20250219 for non-Anthropic upstream --- FAIL: TestApplyClaudeHeaders_NonAnthropicUpstream (0.00s) 

After fix (GREEN):

=== RUN TestApplyClaudeHeaders_NonAnthropicUpstream --- PASS: TestApplyClaudeHeaders_NonAnthropicUpstream (0.00s) === RUN TestApplyClaudeHeaders_AnthropicUpstream --- PASS: TestApplyClaudeHeaders_AnthropicUpstream (0.00s) PASS 

Full test suite:

go test ./... # 35 packages — all PASS, 0 FAIL 

Impact Assessment

  • Affected path: only Claude credentials configured with a non-api.anthropic.com base_url. Credentials pointing at api.anthropic.com are unaffected.
  • Configuration changes: none required.
  • Deployment notes: restart the service to apply.

Checklist

  • Self-tested
  • New tests added covering the bug scenario (fail before fix, pass after)
  • Full test suite passes with no regressions
  • No unrelated changes included
…s-direct-browser-access, x-app, claude-code-20250219 beta) when upstream is not api.anthropic.com.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses a critical issue where Anthropic-specific HTTP headers were inadvertently being sent to all upstream API providers, leading to request failures for non-Anthropic services. The core change involves implementing conditional logic within the header application process, ensuring that these specialized headers are now correctly restricted to only api.anthropic.com. This prevents improper requests to third-party endpoints and enhances compatibility across different API integrations. The solution is thoroughly validated with new test cases that confirm the precise application of headers based on the upstream destination.

Highlights

  • Problem Identification: Anthropic-specific fingerprint headers (Anthropic-Beta, Anthropic-Dangerous-Direct-Browser-Access, X-App) were unconditionally forwarded to all upstream providers, causing third-party APIs to return 400 errors or hang.
  • Root Cause: The applyClaudeHeaders function in claude_executor.go was setting these headers without checking if the target upstream was api.anthropic.com, despite already computing an isAnthropicBase flag.
  • Conditional Header Application: Modified applyClaudeHeaders to ensure that Anthropic-Dangerous-Direct-Browser-Access and X-App headers are only sent when the upstream is api.anthropic.com.
  • Beta Feature Management: Introduced a new helper function removeBetaFeature to conditionally remove the claude-code-20250219 beta feature from the Anthropic-Beta header for non-Anthropic upstreams.
  • New Test Coverage: Added new unit tests (TestApplyClaudeHeaders_NonAnthropicUpstream and TestApplyClaudeHeaders_AnthropicUpstream) to claude_executor_test.go to verify the correct conditional handling of these headers for both Anthropic and non-Anthropic API endpoints.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses the critical issue of Anthropic-specific fingerprint headers being unconditionally forwarded to third-party upstreams. The solution correctly gates these headers based on the isAnthropicBase flag, preventing 400 Improperly formed request errors or hangs with non-Anthropic providers. The addition of the removeBetaFeature helper function is a clean way to manage the Anthropic-Beta header. The new test cases thoroughly validate the fix for both Anthropic and non-Anthropic upstreams, ensuring no regressions. The changes are well-implemented and directly resolve the problem described.

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

for _, beta := range extraBetas {
beta = strings.TrimSpace(beta)
if beta != "" && !existingSet[beta] {
baseBetas += "," + beta
existingSet[beta] = true

P1 Badge Strip request-body Claude beta on non-Anthropic upstreams

applyClaudeHeaders() removes claude-code-20250219 from the default/header-derived beta list, but this merge loop adds it back for any request whose JSON body contains "betas": ["claude-code-20250219"] (see extractAndRemoveBetas() in the same file). For non-api.anthropic.com base_urls, that means the gateway still forwards the incompatible beta and third-party relays can hit the same 400/hang this patch is trying to eliminate. The new tests only cover extraBetas == nil, so this path is still untested.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link

@xkonjin xkonjin left a comment

Choose a reason for hiding this comment

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

Code Review

Overall: Clean, well-scoped fix for a real compatibility issue with third-party upstreams. The approach of conditionally stripping Anthropic-specific headers based on isAnthropicBase is the right pattern.

Observations

removeBetaFeature reuses the input slice backing array (line filtered := parts[:0]). This is fine here since parts is local and discarded after the join, but worth noting: if the input header string were reused elsewhere the shared backing could cause subtle bugs. Not an issue in this call site, just a readability note.

Test coverage is solid. Both the positive case (Anthropic upstream keeps headers) and negative case (non-Anthropic strips them) are covered. The test correctly checks all three header categories: Anthropic-Dangerous-Direct-Browser-Access, X-App, and the claude-code-20250219 beta.

Minor: X-Stainless-* headers are still forwarded to non-Anthropic upstreams. These are also Anthropic/Stainless SDK fingerprint headers. If the goal is to avoid confusing third-party providers, it might be worth gating X-Stainless-Retry-Count, X-Stainless-Runtime-Version, etc. behind isAnthropicBase as well. Third-party providers likely ignore them, but it would be consistent with the intent of the PR.

No bugs or security issues found. LGTM with the minor consideration above.

Copy link
Collaborator

@luispater luispater left a comment

Choose a reason for hiding this comment

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

Summary:
This PR is directionally correct and fixes the unconditional forwarding of Anthropic-Dangerous-Direct-Browser-Access and X-App for non-Anthropic upstreams. However, one blocking gap remains in beta handling.

Key findings:

  • Blocking: claude-code-20250219 may still be forwarded to non-Anthropic upstreams if provided via request-body betas (extraBetas). The current removal runs before merging extraBetas, so the later merge can re-introduce the same beta without checking isAnthropicBase.
  • Non-blocking: Please add a test for the extraBetas path on non-Anthropic upstreams to lock this behavior.

Suggested fix:

  • Either filter extraBetas for non-Anthropic upstreams before merge, or run a final normalization pass after all beta sources are merged (header/default/body/flags), then set Anthropic-Beta.

Test plan:

  • Add/extend unit test:
    • non-Anthropic base URL + extraBetas containing claude-code-20250219 => final Anthropic-Beta must not contain it.
  • Keep existing Anthropic-upstream test to ensure no regression for api.anthropic.com.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants