fix(proxy): preserve and forward OAuth Authorization headers through proxy layer#19912
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Improved OAuth Token Security FixI've updated this PR to use an The Problem with the Original ApproachThe original implementation extracted OAuth tokens from the api_key = auth_header.replace("Bearer ", "")
headers["x-api-key"] = api_keyThis causes two issues:
The SolutionThe update introduces an
Key Changes
Security BenefitThis ensures Claude Code Max OAuth tokens are never set as The existing |
451de0d to
8ad9fe9
Compare
|
Thanks! Minor comments:
I noticed also that your PR only updates the experimental pass-through endpoint. What about the other endpoints (batches, completion, count_tokens, files, skills)? or are the covered by the router flow? |
|
@jquinter Thanks for the thorough review! Here are my responses to each point: 1. Import location (
|
| Endpoint | OAuth Support |
|---|---|
chat/ |
✅ Already has it (via inherited validate_environment()) |
experimental_pass_through/messages/ |
✅ This PR |
batches/ |
❌ Not supported |
completion/ |
❌ Not supported |
count_tokens/ |
❌ Not supported |
files/ |
❌ Not supported |
skills/ |
❌ Not supported |
That said, these endpoints aren't used by Claude Code Max, which is the primary use case for this OAuth fix. Claude Code Max exclusively uses the /v1/messages endpoint (experimental pass-through).
If OAuth support for these other endpoints becomes a requirement, that can be addressed in a follow-up PR using the same pattern (_oauth_ marker + conditional x-api-key setting).
Summary: Reverted the unnecessary model name prefix change. The tests and edge case tests are now complete. Other endpoints can get OAuth support when/if needed.
|
Hey @iamadamreed — just wanted to check in on this PR. I have a use case for OAuth token forwarding and we are using your proposed changes as a fix. It looks like the branch has merge conflicts and the CLA is still pending, which is blocking CI from running fully. Are you still planning to update this? Happy to help if needed! |
Yeah, I constantly forget to toggle between my personal and work account, and then do shit like this lol. I am resolving this now. |
…proxy layer PR BerriAI#21039 fixed OAuth token handling at the LLM layer (Authorization: Bearer instead of x-api-key), but the proxy layer still strips the Authorization header in clean_headers() before it reaches the Anthropic code. This breaks OAuth for proxy users (e.g., Claude Code Max through LiteLLM proxy). Changes: - Add is_anthropic_oauth_key() helper to detect OAuth tokens (sk-ant-oat*) - Preserve OAuth Authorization headers in clean_headers() instead of stripping - Forward OAuth Authorization via ProviderSpecificHeader in add_provider_specific_headers_to_request() so tokens only reach Anthropic-compatible providers (anthropic, bedrock, vertex_ai) Fixes BerriAI#19618
5c739b9 to
19ee11c
Compare
PR Reworked — Rebased on main, scoped to proxy layer only@kesensoy @jquinter This PR has been fully reworked. Here's what changed: What happenedWhile this PR was waiting on review, PR #21039 landed on However, #21039 did not fix the proxy layer. The proxy's What this PR now doesRebased on
@jquinter — re: your earlier review pointsAll 4 points are now moot since the
CI noteThe |
|
This is needed, PR #21039 was indeed incomplete: it missed the proxy layer. |
|
@greptile-apps review this PR |
Greptile SummaryThis PR fixes Anthropic OAuth token (
Confidence Score: 4/5
|
| Filename | Overview |
|---|---|
| litellm/llms/anthropic/common_utils.py | Adds is_anthropic_oauth_key() helper function to detect OAuth token prefix (sk-ant-oat*), handling both raw and Bearer-prefixed formats. Minor PEP 8 blank line issue. |
| litellm/proxy/litellm_pre_call_utils.py | Modifies clean_headers() to preserve Authorization when it contains Anthropic OAuth token, and add_provider_specific_headers_to_request() to forward it via ProviderSpecificHeader scoped to Anthropic-compatible providers. Introduces provider-specific logic in the proxy layer. |
| tests/test_litellm/llms/anthropic/test_anthropic_common_utils.py | Adds 10 well-structured unit tests covering is_anthropic_oauth_key, clean_headers OAuth preservation, and add_provider_specific_headers_to_request forwarding. All tests are mocked with no network calls. |
Sequence Diagram
sequenceDiagram
participant Client
participant Proxy as LiteLLM Proxy
participant Clean as clean_headers
participant PSH as add_provider_specific_headers
participant Main as litellm.completion
participant API as Anthropic API
Client->>Proxy: Request with OAuth Authorization header
Proxy->>Proxy: user_api_key_auth authenticates request
Proxy->>Clean: clean_headers(request.headers)
Note over Clean: is_anthropic_oauth_key detects<br/>OAuth prefix, preserves header
Clean-->>Proxy: headers with authorization kept
Proxy->>PSH: add_provider_specific_headers(data, headers)
Note over PSH: Wraps OAuth auth in<br/>ProviderSpecificHeader scoped to<br/>anthropic, bedrock, vertex_ai
PSH-->>Proxy: data with provider_specific_header
Proxy->>Main: litellm.completion(data)
Note over Main: ProviderSpecificHeaderUtils<br/>matches provider, merges headers
Main->>API: Forwards OAuth auth header
Last reviewed commit: 19ee11c
| """ | ||
| Removes litellm api key from headers | ||
| """ | ||
| from litellm.llms.anthropic.common_utils import is_anthropic_oauth_key |
There was a problem hiding this comment.
Provider-specific import in proxy layer
This introduces Anthropic-specific logic (is_anthropic_oauth_key) into the proxy's clean_headers() function, which is a general-purpose proxy utility. The project's custom instructions recommend avoiding provider-specific code outside of the llms/ directory.
That said, I see that add_provider_specific_headers_to_request() already contains Anthropic-specific header handling (the ANTHROPIC_API_HEADERS loop), so this PR extends an existing pattern rather than introducing a new one. If the maintainers want to address this in the future, consider extracting a more generic "provider pass-through credential" abstraction that could work for any provider, rather than hardcoding Anthropic OAuth detection in the proxy layer.
Context Used: Rule from dashboard - What: Avoid writing provider-specific code outside of the llms/ directory.
Why: This practice ensur... (source)
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
| return value.startswith(ANTHROPIC_OAUTH_TOKEN_PREFIX) | ||
|
|
||
| def optionally_handle_anthropic_oauth( |
There was a problem hiding this comment.
Missing blank line between functions
PEP 8 requires two blank lines between top-level function definitions. There's only one blank line separating is_anthropic_oauth_key from optionally_handle_anthropic_oauth.
| return value.startswith(ANTHROPIC_OAUTH_TOKEN_PREFIX) | |
| def optionally_handle_anthropic_oauth( | |
| return value.startswith(ANTHROPIC_OAUTH_TOKEN_PREFIX) | |
| def optionally_handle_anthropic_oauth( |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
8d50956
into
BerriAI:litellm_oss_staging_02_17_2026
- Remove aawm.1 entry (absorbed by upstream PR BerriAI#19912 in v1.81.13) - Update base version references from v1.81.13 to upstream/main (v1.81.14) - Add aawm.3 (header propagation) and aawm.4 (agent identity) entries - Add aawm.5 (Gemini OAuth) entry with updated description reflecting that only ya29.* is new; Anthropic OAuth now handled upstream - Add "Dropped Patches" section documenting aawm.1 absorption - Update patch count to 4 active patches (aawm.2, aawm.3, aawm.4, aawm.5) - Update Dockerfile overlay example to include all 6 patched files - Update branch strategy table to include aawm/patches-rebase
- Remove aawm.1 entry (absorbed by upstream PR BerriAI#19912 in v1.81.13) - Update base version references from v1.81.13 to upstream/main (v1.81.14) - Add aawm.3 (header propagation) and aawm.4 (agent identity) entries - Add aawm.5 (Gemini OAuth) entry with updated description reflecting that only ya29.* is new; Anthropic OAuth now handled upstream - Add "Dropped Patches" section documenting aawm.1 absorption - Update patch count to 4 active patches (aawm.2, aawm.3, aawm.4, aawm.5) - Update Dockerfile overlay example to include all 6 patched files - Update branch strategy table to include aawm/patches-rebase
Summary
PR #21039 fixed OAuth token handling at the LLM layer (using
Authorization: Bearerinstead ofx-api-key), but the proxy layer still strips theAuthorizationheader inclean_headers()before it ever reaches the Anthropic code. This breaks OAuth for proxy users (e.g., Claude Code Max through LiteLLM proxy).This PR is the complementary proxy-layer fix.
Relevant issues
Fixes #19618
Changes
litellm/llms/anthropic/common_utils.pyis_anthropic_oauth_key()helper to detect OAuth token prefix (sk-ant-oat*)litellm/proxy/litellm_pre_call_utils.pyclean_headers()to preserve Authorization header when it contains an Anthropic OAuth tokenlitellm/proxy/litellm_pre_call_utils.pyadd_provider_specific_headers_to_request()to forward OAuth Authorization viaProviderSpecificHeaderscoped to Anthropic-compatible providersHow it works
clean_headers()normally strips theAuthorizationheader (it's in_SPECIAL_HEADERS_CACHE). With this fix, it preserves it when the value contains an Anthropic OAuth token (sk-ant-oat*)add_provider_specific_headers_to_request()picks up the preserved OAuth Authorization header and wraps it in aProviderSpecificHeadertargeting onlyanthropic,bedrock, andvertex_aiprovidersRelationship to #21039
get_anthropic_headers,optionally_handle_anthropic_oauth, passthrough)clean_headers,add_provider_specific_headers_to_request)Without this PR, #21039's fixes never take effect for proxy users because the
Authorizationheader is stripped before reaching the Anthropic code.Pre-Submission checklist
make test-unitis_anthropic_oauth_key, 5 for proxy-layer forwarding)Type
🐛 Bug Fix
Test plan
test_anthropic_common_utils.pyclean_headerspreserves OAuth Authorization, strips non-OAuthadd_provider_specific_headers_to_requestforwards OAuth only to Anthropic-compatible providersis_anthropic_oauth_keyhandles raw tokens, Bearer format, None, empty string, regular keys, case sensitivity