From 3d4ba87e8c0872e9f4f9be0c858f27dc1c4c1618 Mon Sep 17 00:00:00 2001 From: Deepanshu Date: Tue, 1 Sep 2026 10:40:57 -0400 Subject: [PATCH] fix(proxy): guard extract_key_hash/extract_key_alias against a non-Mapping metadata field Both called active.get(...) after an `or EMPTY_MAPPING` fallback that only triggers on a falsy value, so a truthy non-Mapping (metadata can arrive as an unparsed JSON string on multipart/extra_body routes) raised AttributeError instead of falling through. order_tags_for_identity_resolution already guarded the same pattern; applied the same isinstance check here. Bugbot finding on commit 41be4a8782. --- litellm/proxy/hooks/tag_rate_limits_shared.py | 4 ++-- .../proxy/hooks/test_tag_rate_limits_shared.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/hooks/tag_rate_limits_shared.py b/litellm/proxy/hooks/tag_rate_limits_shared.py index c16003cd3bf..6fa1cadac49 100644 --- a/litellm/proxy/hooks/tag_rate_limits_shared.py +++ b/litellm/proxy/hooks/tag_rate_limits_shared.py @@ -238,7 +238,7 @@ def extract_key_hash(request_kwargs: Mapping[str, object], metadata_variable_nam the plain name is already the hashed token (see `litellm_pre_call_utils.py`). """ active: Final = request_kwargs.get(metadata_variable_name) or EMPTY_MAPPING - key_hash: Final = active.get("user_api_key") + key_hash: Final = active.get("user_api_key") if isinstance(active, Mapping) else None return key_hash if isinstance(key_hash, str) else None @@ -249,7 +249,7 @@ def extract_key_alias(request_kwargs: Mapping[str, object], metadata_variable_na `metadata["user_api_key_alias"]` to `user_api_key_dict.key_alias` (see `litellm_pre_call_utils.py`).""" active: Final = request_kwargs.get(metadata_variable_name) or EMPTY_MAPPING - key_alias: Final = active.get("user_api_key_alias") + key_alias: Final = active.get("user_api_key_alias") if isinstance(active, Mapping) else None return key_alias if isinstance(key_alias, str) else None diff --git a/tests/test_litellm/proxy/hooks/test_tag_rate_limits_shared.py b/tests/test_litellm/proxy/hooks/test_tag_rate_limits_shared.py index 7bb9d04fef0..38747090c59 100644 --- a/tests/test_litellm/proxy/hooks/test_tag_rate_limits_shared.py +++ b/tests/test_litellm/proxy/hooks/test_tag_rate_limits_shared.py @@ -12,6 +12,7 @@ from litellm.proxy.hooks.tag_rate_limits_shared import ( bucket_ttl_seconds, entry_applies, extract_identity, + extract_key_alias, extract_key_hash, fixed_length_identity, order_tags_for_identity_resolution, @@ -125,6 +126,19 @@ def test_extract_key_hash_reads_metadata_when_it_is_the_authoritative_field(): assert extract_key_hash(request_kwargs, "metadata") == "real-hash" +def test_extract_key_hash_ignores_a_non_mapping_authoritative_field(): + """Bugbot finding: metadata can arrive as an unparsed JSON string (see + apply_client_tag_policy_pre_auth's own docstring on multipart/extra_body + routes); a truthy non-Mapping must not reach .get() and crash.""" + request_kwargs = {"metadata": '{"user_api_key": "forged"}'} + assert extract_key_hash(request_kwargs, "metadata") is None + + +def test_extract_key_alias_ignores_a_non_mapping_authoritative_field(): + request_kwargs = {"metadata": '{"user_api_key_alias": "forged"}'} + assert extract_key_alias(request_kwargs, "metadata") is None + + # --------------------------------------------------------------------------- # resolve_success_event_metadata_variable_name -- veria-ai finding surfaced on # PR #38347: a caller-supplied, non-empty litellm_metadata must not be