From 22f84b70ef60a7ebb2b1bcd8151c2db34277e532 Mon Sep 17 00:00:00 2001 From: shadowmodder Date: Thu, 5 Nov 2026 12:04:59 -0600 Subject: [PATCH] fix(proxy): redact restored snapshot when fallback guardrail blocks --- litellm/proxy/common_request_processing.py | 4 +- .../proxy/test_common_request_processing.py | 71 +++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/common_request_processing.py b/litellm/proxy/common_request_processing.py index cab9b7570eb..371a63d2e40 100644 --- a/litellm/proxy/common_request_processing.py +++ b/litellm/proxy/common_request_processing.py @@ -2852,7 +2852,9 @@ class ProxyBaseLLMRequestProcessing: if is_hard_per_model_limit(fallback_exc): raise continue - except BaseException: + except BaseException as e: + if is_guardrail_intervention(e): + redact_blocked_request_body_snapshot(rate_limited_data) self.data = rate_limited_data raise diff --git a/tests/unit/proxy/test_common_request_processing.py b/tests/unit/proxy/test_common_request_processing.py index df746871d02..ca3d66ee679 100644 --- a/tests/unit/proxy/test_common_request_processing.py +++ b/tests/unit/proxy/test_common_request_processing.py @@ -7975,6 +7975,77 @@ class TestPreCallWithFallbacksOnLocalRateLimit: assert processor.data["model"] == fallback_model assert call_count == 2 + @pytest.mark.asyncio + async def test_fallback_guardrail_block_redacts_restored_request_body(self): + from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing + from litellm.proxy.common_utils.proxy_rate_limit_error import ProxyRateLimitError + from litellm.proxy.litellm_pre_call_utils import redact_blocked_request_body_snapshot + + primary_model = "gpt-4" + fallback_model = "gpt-3.5-turbo" + secret = "glpat-FAKE-NOT-A-REAL-TOKEN-2222" + raw_body = { + "model": primary_model, + "messages": [{"role": "user", "content": f"my token is {secret}"}], + } + processor = ProxyBaseLLMRequestProcessing( + data={ + "model": primary_model, + "messages": raw_body["messages"], + "proxy_server_request": { + "url": "http://localhost/v1/chat/completions", + "method": "POST", + "body": dict(raw_body), + }, + } + ) + + async def mock_pre_call_logic(**kwargs): + if processor.data.get("model") == primary_model: + raise ProxyRateLimitError( + detail="TPM limit exceeded for gpt-4", + headers={"retry-after": "30"}, + ) + redact_blocked_request_body_snapshot(processor.data) + raise GuardrailRaisedException( + guardrail_name="block-secrets", + message="Content blocked: secret detected", + blocked_content=True, + ) + + mock_router = route_entry_points_to_stubs(MagicMock()) + mock_router.fallbacks = [{primary_model: [fallback_model]}] + + with ( + patch.object( + processor, + "common_processing_pre_call_logic", + side_effect=mock_pre_call_logic, + ), + pytest.raises(GuardrailRaisedException), + ): + await processor._pre_call_with_fallbacks( + request=MagicMock(), + general_settings={}, + proxy_logging_obj=MagicMock(), + user_api_key_dict=MagicMock(router_settings=None), + version=None, + proxy_config=MagicMock(), + user_model=None, + user_temperature=None, + user_request_timeout=None, + user_max_tokens=None, + user_api_base=None, + model=primary_model, + route_type="acompletion", + llm_router=mock_router, + ) + + assert processor.data["proxy_server_request"]["body"] == { + "model": primary_model, + "[redacted]": "blocked by guardrail", + }, "fallback restoration put the unredacted blocked prompt back in the spend-log snapshot" + @pytest.mark.asyncio async def test_local_rate_limit_fallback_skips_an_aliased_embedding_only_model(self): from litellm.proxy.common_request_processing import ProxyBaseLLMRequestProcessing