diff --git a/litellm/proxy/auth/auth_checks.py b/litellm/proxy/auth/auth_checks.py index 6ddf2cfeb20e..852c5b4d6b59 100644 --- a/litellm/proxy/auth/auth_checks.py +++ b/litellm/proxy/auth/auth_checks.py @@ -699,6 +699,12 @@ async def common_checks( if valid_token is not None: from litellm.proxy.litellm_pre_call_utils import LiteLLMProxyRequestSetup + # GH#30629: pre-seed litellm_metadata for bedrock routes so + # apply_key_tags_pre_auth writes tags there instead of + # leaking them into the provider-facing metadata field + if "bedrock" in route: + request_body.setdefault("litellm_metadata", {}) + LiteLLMProxyRequestSetup.apply_key_tags_pre_auth( request_data=request_body, user_api_key_dict=valid_token, diff --git a/litellm/proxy/litellm_pre_call_utils.py b/litellm/proxy/litellm_pre_call_utils.py index 177fced5cd4f..200c145792a4 100644 --- a/litellm/proxy/litellm_pre_call_utils.py +++ b/litellm/proxy/litellm_pre_call_utils.py @@ -108,6 +108,7 @@ def parse_cache_control(cache_control): LITELLM_METADATA_ROUTES = ( "batches", + "bedrock", "/v1/messages", "responses", "files", diff --git a/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py b/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py index 362f4986c626..379e219b29bf 100644 --- a/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py +++ b/tests/test_litellm/proxy/pass_through_endpoints/test_pass_through_endpoints.py @@ -2682,15 +2682,19 @@ async def test_add_litellm_data_to_request_adds_headers_to_metadata(): version="1.0", ) - # Verify headers are added to metadata for guardrails - assert "metadata" in result, "metadata should be present in result" - assert "headers" in result["metadata"], "headers should be present in metadata" + # Verify headers are added to litellm_metadata for guardrails. + # Bedrock passthrough uses litellm_metadata to prevent key-level + # tags from leaking into the provider payload (GH#30629). + assert "litellm_metadata" in result, "litellm_metadata should be present in result" + assert ( + "headers" in result["litellm_metadata"] + ), "headers should be present in litellm_metadata" assert isinstance( - result["metadata"]["headers"], dict + result["litellm_metadata"]["headers"], dict ), "headers should be a dictionary" # Verify specific headers are accessible (important for guardrails) - headers = result["metadata"]["headers"] + headers = result["litellm_metadata"]["headers"] assert ( "user-agent" in headers or "User-Agent" in headers ), "User-Agent header should be accessible in metadata" diff --git a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py index ec262d75ab89..80c1205a8aef 100644 --- a/tests/test_litellm/proxy/test_litellm_pre_call_utils.py +++ b/tests/test_litellm/proxy/test_litellm_pre_call_utils.py @@ -96,6 +96,16 @@ def test_returns_metadata_for_embeddings(self): request = self._make_request("/v1/embeddings") assert _get_metadata_variable_name(request) == "metadata" + def test_returns_litellm_metadata_for_bedrock_invoke(self): + # GH#30629: bedrock passthrough must use litellm_metadata + # to prevent key-level tags from leaking into provider body + request = self._make_request("/bedrock/model/us.anthropic.claude-sonnet-4-6/invoke") + assert _get_metadata_variable_name(request) == "litellm_metadata" + + def test_returns_litellm_metadata_for_bedrock_converse(self): + request = self._make_request("/bedrock/model/us.anthropic.claude-sonnet-4-6/converse") + assert _get_metadata_variable_name(request) == "litellm_metadata" + def test_get_enforced_params_for_service_account_settings(): """