fix: address issue #24436

This commit is contained in:
RoyVivat 2026-03-23 16:30:35 -07:00
parent 50f88c8642
commit 147d80985f
2 changed files with 100 additions and 1 deletions

View file

@ -1,7 +1,10 @@
from typing import Dict, Optional
from litellm.llms.anthropic.common_utils import is_anthropic_oauth_key
from litellm.types.utils import ProviderSpecificHeader
_ANTHROPIC_PROVIDER = "anthropic"
class ProviderSpecificHeaderUtils:
@staticmethod
@ -14,6 +17,10 @@ class ProviderSpecificHeaderUtils:
Supports comma-separated provider lists for headers that work across multiple providers.
Anthropic OAuth tokens (sk-ant-oat*) in the Authorization header are stripped for
non-Anthropic providers to prevent them from overriding provider-specific auth
(e.g. AWS SigV4 for Bedrock, service account credentials for Vertex AI).
Returns:
Dict: The provider specific headers for the given custom llm provider
"""
@ -24,6 +31,16 @@ class ProviderSpecificHeaderUtils:
provider_list = [p.strip() for p in stored_providers.split(",")]
if custom_llm_provider in provider_list:
return provider_specific_header.get("extra_headers", {})
headers = provider_specific_header.get("extra_headers", {})
# Anthropic OAuth tokens must not be forwarded to non-Anthropic providers.
# Forwarding them would overwrite provider-specific auth headers
# (e.g. Bedrock's SigV4 Authorization, Vertex AI service-account auth).
if custom_llm_provider != _ANTHROPIC_PROVIDER:
headers = {
k: v
for k, v in headers.items()
if not (k.lower() == "authorization" and is_anthropic_oauth_key(v))
}
return headers
return {}

View file

@ -112,3 +112,85 @@ class TestProviderSpecificHeaderUtils:
provider_specific_header, None
)
assert result == {}
def test_anthropic_oauth_token_not_forwarded_to_bedrock(self):
"""
Regression test for https://github.com/BerriAI/litellm/issues/24436.
When Claude Code sets an Anthropic OAuth token (sk-ant-oat*) in Authorization,
it must NOT reach Bedrock forwarding it overwrites the AWS SigV4 Authorization
header and causes a 403 from Bedrock.
"""
oauth_token = "Bearer sk-ant-oat01-abc123xyz"
provider_specific_header: ProviderSpecificHeader = {
"custom_llm_provider": "anthropic,bedrock,vertex_ai",
"extra_headers": {
"Authorization": oauth_token,
"anthropic-beta": "some-beta-feature",
},
}
# Anthropic should receive the OAuth token
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, "anthropic"
)
assert result["Authorization"] == oauth_token
assert result["anthropic-beta"] == "some-beta-feature"
# Bedrock must NOT receive the OAuth token (it would overwrite SigV4 auth)
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, "bedrock"
)
assert "Authorization" not in result
assert result.get("anthropic-beta") == "some-beta-feature"
# Vertex AI must NOT receive the OAuth token
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, "vertex_ai"
)
assert "Authorization" not in result
assert result.get("anthropic-beta") == "some-beta-feature"
def test_anthropic_oauth_token_not_forwarded_to_bedrock_converse(self):
"""Anthropic OAuth token must not be forwarded to bedrock_converse either."""
oauth_token = "Bearer sk-ant-oat02-xyz789"
provider_specific_header: ProviderSpecificHeader = {
"custom_llm_provider": "anthropic,bedrock,bedrock_converse,vertex_ai",
"extra_headers": {"Authorization": oauth_token},
}
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, "bedrock_converse"
)
assert result == {}
def test_non_oauth_authorization_still_forwarded_to_non_anthropic(self):
"""A plain Bearer token (not Anthropic OAuth) should still pass through."""
plain_token = "Bearer some-regular-api-key"
provider_specific_header: ProviderSpecificHeader = {
"custom_llm_provider": "openai,azure",
"extra_headers": {"Authorization": plain_token},
}
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, "openai"
)
assert result["Authorization"] == plain_token
def test_anthropic_oauth_token_raw_format_not_forwarded_to_bedrock(self):
"""Anthropic OAuth token in raw format (without 'Bearer ' prefix) is also stripped."""
raw_oauth_token = "sk-ant-oat01-abc123"
provider_specific_header: ProviderSpecificHeader = {
"custom_llm_provider": "anthropic,bedrock,vertex_ai",
"extra_headers": {"Authorization": raw_oauth_token},
}
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, "bedrock"
)
assert "Authorization" not in result
result = ProviderSpecificHeaderUtils.get_provider_specific_headers(
provider_specific_header, "anthropic"
)
assert result["Authorization"] == raw_oauth_token