mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix: stop provider-scoped headers leaking across fallback hops
completion() aliased the caller's header mapping instead of copying it, then merged the provider-scoped headers into that same object. The router shares one header dict across fallback attempts, so the credential written on an Anthropic attempt was still present when a later Bedrock or Vertex attempt read the dict, defeating the provider scoping. Copy the mapping before merging so each attempt sees only its own headers.
This commit is contained in:
parent
b1b29e5cb0
commit
43143933d9
2 changed files with 48 additions and 2 deletions
|
|
@ -5100,8 +5100,7 @@ def completion(
|
|||
ensure_alternating_roles: Final[bool | None] = kwargs.get("ensure_alternating_roles", None)
|
||||
user_continue_message: Final[ChatCompletionUserMessage | None] = kwargs.get("user_continue_message", None)
|
||||
assistant_continue_message: ChatCompletionAssistantMessage | None = kwargs.get("assistant_continue_message", None)
|
||||
if headers is None:
|
||||
headers = {}
|
||||
headers = {} if headers is None else dict(headers)
|
||||
if extra_headers is not None:
|
||||
headers.update(extra_headers)
|
||||
# Inject proxy auth headers if configured
|
||||
|
|
|
|||
|
|
@ -2754,3 +2754,50 @@ def test_completion_default_api_base_sends_prompt_cache_breakpoint_for_gpt_5_6()
|
|||
{"type": "text", "text": "sys", "prompt_cache_breakpoint": {"mode": "explicit"}}
|
||||
]
|
||||
assert request_body["extra_body"]["prompt_cache_options"] == {"mode": "explicit"}
|
||||
|
||||
|
||||
_SUBSCRIPTION_OAUTH_CREDENTIAL = "Bearer sk-ant-oat01-fake-subscription-token-for-testing-0123456789"
|
||||
|
||||
|
||||
def _scoped_headers_for_oauth_request():
|
||||
from litellm.types.utils import ProviderSpecificHeader
|
||||
|
||||
return [
|
||||
ProviderSpecificHeader(
|
||||
custom_llm_provider="anthropic,bedrock,vertex_ai",
|
||||
extra_headers={"anthropic-version": "2023-06-01"},
|
||||
),
|
||||
ProviderSpecificHeader(
|
||||
custom_llm_provider="anthropic",
|
||||
extra_headers={"authorization": _SUBSCRIPTION_OAUTH_CREDENTIAL},
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
def _run_anthropic_hop_with_shared_headers(shared_headers):
|
||||
litellm.completion(
|
||||
model="anthropic/claude-3-5-sonnet-20240620",
|
||||
messages=[{"role": "user", "content": "Say OK"}],
|
||||
extra_headers=shared_headers,
|
||||
provider_specific_header=_scoped_headers_for_oauth_request(),
|
||||
api_key="sk-fake-anthropic-key",
|
||||
mock_response="OK",
|
||||
)
|
||||
|
||||
|
||||
def test_completion_does_not_mutate_caller_supplied_headers():
|
||||
shared_headers = {"x-tenant": "acme"}
|
||||
|
||||
_run_anthropic_hop_with_shared_headers(shared_headers)
|
||||
|
||||
assert shared_headers == {"x-tenant": "acme"}
|
||||
|
||||
|
||||
def test_anthropic_oauth_credential_does_not_persist_into_next_provider_hop():
|
||||
shared_headers = {"x-tenant": "acme"}
|
||||
|
||||
_run_anthropic_hop_with_shared_headers(shared_headers)
|
||||
|
||||
leaked = [name for name, value in shared_headers.items() if value == _SUBSCRIPTION_OAUTH_CREDENTIAL]
|
||||
assert leaked == []
|
||||
assert "anthropic-version" not in shared_headers
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue