mirror of
https://github.com/BerriAI/litellm.git
synced 2026-11-14 09:03:16 +00:00
fix(proxy): redact body snapshot on pre-call guardrail failure to protect spend logs (#50127)
This commit is contained in:
parent
79e63d3e06
commit
da3176cceb
3 changed files with 63 additions and 6 deletions
|
|
@ -286,6 +286,7 @@ from litellm.proxy.litellm_pre_call_utils import (
|
|||
LiteLLMProxyRequestSetup,
|
||||
add_litellm_data_to_request,
|
||||
get_metadata_variable_name,
|
||||
redact_proxy_server_request_body_on_guardrail_block,
|
||||
refresh_proxy_server_request_body_snapshot,
|
||||
reject_url_valued_destination,
|
||||
sanitize_for_log,
|
||||
|
|
@ -2670,12 +2671,17 @@ class ProxyBaseLLMRequestProcessing:
|
|||
prefetch_model = self.data.get("model")
|
||||
if llm_router is not None and isinstance(prefetch_model, str):
|
||||
RoutingContext(llm_router).arm_read_prefetch(prefetch_model, self.data)
|
||||
self.data = await proxy_logging_obj.pre_call_hook(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
data=self.data,
|
||||
call_type=route_type,
|
||||
skip_guardrails=skip_guardrails,
|
||||
)
|
||||
try:
|
||||
self.data = await proxy_logging_obj.pre_call_hook(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
data=self.data,
|
||||
call_type=route_type,
|
||||
skip_guardrails=skip_guardrails,
|
||||
)
|
||||
except Exception:
|
||||
if isinstance(self.data, dict):
|
||||
redact_proxy_server_request_body_on_guardrail_block(self.data)
|
||||
raise
|
||||
await _enforce_guardrail_added_tag_budgets(
|
||||
data=self.data,
|
||||
tags_before_guardrails=self._tags_before_guardrails,
|
||||
|
|
|
|||
|
|
@ -2608,6 +2608,26 @@ def refresh_proxy_server_request_body_snapshot(
|
|||
)
|
||||
|
||||
|
||||
def redact_proxy_server_request_body_on_guardrail_block(
|
||||
data: MutableMapping[str, object],
|
||||
) -> None:
|
||||
"""
|
||||
Redact prompt/messages fields in data['proxy_server_request']['body'] when a pre-call
|
||||
guardrail blocks a request (#50127).
|
||||
This ensures that raw PII or secret content that triggered a guardrail rejection is not
|
||||
persisted in plain text in LiteLLM_SpendLogs via async_post_call_failure_hook.
|
||||
"""
|
||||
proxy_server_request: Final = data.get("proxy_server_request")
|
||||
if not isinstance(proxy_server_request, dict):
|
||||
return
|
||||
body: Final = proxy_server_request.get("body")
|
||||
if not isinstance(body, dict):
|
||||
return
|
||||
for field in ("messages", "prompt", "input"):
|
||||
if field in body:
|
||||
body[field] = "[REDACTED - BLOCKED BY GUARDRAIL]"
|
||||
|
||||
|
||||
async def add_litellm_data_to_request(
|
||||
data: dict,
|
||||
request: Request,
|
||||
|
|
|
|||
|
|
@ -1391,6 +1391,37 @@ def test_refresh_proxy_server_request_body_snapshot_picks_up_guardrail_masking()
|
|||
assert "123-45-6789" not in json.dumps(refreshed_body)
|
||||
|
||||
|
||||
def test_redact_proxy_server_request_body_on_guardrail_block():
|
||||
"""
|
||||
Regression: When a pre-call guardrail blocks a request (e.g. secret or PII detected),
|
||||
pre_call_hook raises an exception. The raw snapshot in proxy_server_request['body']
|
||||
must be redacted so raw blocked text is not written into SpendLogs in plain text (#50127).
|
||||
"""
|
||||
from litellm.proxy.litellm_pre_call_utils import (
|
||||
redact_proxy_server_request_body_on_guardrail_block,
|
||||
)
|
||||
|
||||
data = {
|
||||
"model": "gpt-4",
|
||||
"messages": [{"role": "user", "content": "secret password is supersecret"}],
|
||||
"proxy_server_request": {
|
||||
"url": "http://localhost/v1/chat/completions",
|
||||
"body": {
|
||||
"model": "gpt-4",
|
||||
"messages": [{"role": "user", "content": "secret password is supersecret"}],
|
||||
"prompt": "supersecret prompt",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
redact_proxy_server_request_body_on_guardrail_block(data)
|
||||
|
||||
body = data["proxy_server_request"]["body"]
|
||||
assert body["messages"] == "[REDACTED - BLOCKED BY GUARDRAIL]"
|
||||
assert body["prompt"] == "[REDACTED - BLOCKED BY GUARDRAIL]"
|
||||
assert "supersecret" not in json.dumps(body)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_add_litellm_data_to_request_strips_string_encoded_admin_injection():
|
||||
"""Regression: metadata arriving as a JSON string (multipart/form-data or
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue