From 768f14ead8eed41eceaee782906c897aad204338 Mon Sep 17 00:00:00 2001 From: Sergey Kalashian Date: Tue, 13 Oct 2026 10:35:28 -0700 Subject: [PATCH] fix(proxy): accept workspace_id as a metadata tag #31203 banned bare workspace_id in request metadata, but the Claude Platform workspace pin is read only from top-level call params, so the metadata ban blocked a common spend-tracking tag without guarding anything. Root and extra_body workspace_id and every other workspace alias stay banned. --- litellm/proxy/auth/auth_utils.py | 23 +++++++++++-- tests/unit/proxy/auth/test_auth_utils.py | 42 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/auth/auth_utils.py b/litellm/proxy/auth/auth_utils.py index e4fe88690f5..1fad4d3fbee 100644 --- a/litellm/proxy/auth/auth_utils.py +++ b/litellm/proxy/auth/auth_utils.py @@ -313,6 +313,15 @@ _NESTED_CONFIG_KEYS: Final[tuple[str, ...]] = ("litellm_embedding_config", "extr # was an unintentional bypass. _NESTED_METADATA_KEYS: Final[tuple[str, ...]] = ("metadata", "litellm_metadata") +# Bedrock Claude Platform reads its workspace pin only from top-level call params, never from +# metadata, where `workspace_id` is a common caller tag (spend logs, traces). +_METADATA_CALLER_TAGS: Final[frozenset[str]] = frozenset({"workspace_id"}) + + +def _without_metadata_caller_tags(metadata: Mapping[str, object]) -> Mapping[str, object]: + return MappingProxyType({key: value for key, value in metadata.items() if key not in _METADATA_CALLER_TAGS}) + + # Banned request-body params. The same list applies to every entry in # ``_NESTED_CONFIG_KEYS`` (dicts spread as ``**kwargs`` into outbound # calls) and ``_NESTED_METADATA_KEYS`` (dicts read directly by integration @@ -640,10 +649,18 @@ def is_request_body_safe( for metadata_key in _NESTED_METADATA_KEYS: metadata = _coerce_metadata_to_dict(request_body.get(metadata_key)) if metadata is not None: - _check_banned_params(metadata, general_settings, llm_router, model, manages_deployments=manages_deployments) + _check_banned_params( + _without_metadata_caller_tags(metadata), + general_settings, + llm_router, + model, + manages_deployments=manages_deployments, + ) if any(isinstance(key, str) and key.startswith(f"{metadata_key}[") for key in request_body): _check_banned_params( - extract_nested_form_metadata(form_data=request_body, prefix=f"{metadata_key}["), + _without_metadata_caller_tags( + extract_nested_form_metadata(form_data=request_body, prefix=f"{metadata_key}[") + ), general_settings, llm_router, model, @@ -665,7 +682,7 @@ def is_request_body_safe( litellm_params_metadata: Final = _coerce_metadata_to_dict(litellm_params.get("metadata")) if litellm_params_metadata is not None: _check_banned_params( - litellm_params_metadata, + _without_metadata_caller_tags(litellm_params_metadata), general_settings, llm_router, model, diff --git a/tests/unit/proxy/auth/test_auth_utils.py b/tests/unit/proxy/auth/test_auth_utils.py index c224cd08663..30fe4c585b4 100644 --- a/tests/unit/proxy/auth/test_auth_utils.py +++ b/tests/unit/proxy/auth/test_auth_utils.py @@ -3119,6 +3119,48 @@ class TestIsRequestBodySafeBlocksClaudePlatformWorkspaceOverride: is True ) + @pytest.mark.parametrize( + "request_body", + [ + pytest.param({"metadata": {"workspace_id": "ws-1"}}, id="metadata"), + pytest.param({"litellm_metadata": {"workspace_id": "ws-1"}}, id="litellm_metadata"), + pytest.param({"litellm_metadata": '{"workspace_id": "ws-1"}'}, id="litellm_metadata-json"), + pytest.param({"metadata[workspace_id]": "ws-1"}, id="form-metadata"), + pytest.param({"litellm_params": {"metadata": {"workspace_id": "ws-1"}}}, id="litellm_params-metadata"), + ], + ) + def test_workspace_id_metadata_tag_is_accepted(self, request_body): + assert ( + is_request_body_safe( + request_body={"model": "gpt-4", **request_body}, + general_settings={}, + llm_router=None, + model="gpt-4", + ) + is True + ) + + @pytest.mark.parametrize( + ("request_body", "rejected"), + [ + pytest.param({"extra_body": {"workspace_id": "wrkspc_attacker"}}, "workspace_id", id="extra_body"), + pytest.param({"metadata": {"aws_workspace_id": "wrkspc_attacker"}}, "aws_workspace_id", id="alias"), + pytest.param( + {"metadata": {"workspace_id": "ws-1", "langfuse_host": "https://attacker.example"}}, + "langfuse_host", + id="tag-beside-banned", + ), + ], + ) + def test_metadata_tag_exemption_stays_narrow(self, request_body, rejected): + with pytest.raises(ValueError, match=rejected): + is_request_body_safe( + request_body={"model": "gpt-4", **request_body}, + general_settings={}, + llm_router=None, + model="gpt-4", + ) + class TestIsRequestBodySafeBlocksFireworksForwardUserId: @pytest.mark.parametrize("value", [True, False])