fix(logging): redact native system-prompt keys in request-body snapshots
Some checks failed
Unit Tests: Security / security (push) Has been cancelled
Unit Tests: Proxy DB Operations / assert-shard-coverage (push) Has been cancelled
Unit Tests: Proxy DB Operations / auth-checks (push) Has been cancelled
Unit Tests: Proxy DB Operations / budgets (push) Has been cancelled
Unit Tests: Proxy DB Operations / custom-logging (push) Has been cancelled
Unit Tests: Proxy DB Operations / db-and-spend (push) Has been cancelled
Unit Tests: Proxy DB Operations / endpoints-and-responses (push) Has been cancelled
Unit Tests: Proxy DB Operations / guardrails-hooks (push) Has been cancelled
Unit Tests: Proxy DB Operations / jwt-and-keys (push) Has been cancelled
Unit Tests: Proxy DB Operations / key-generation (push) Has been cancelled
Unit Tests: Proxy DB Operations / logging-misc (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-runtime (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-server-core (push) Has been cancelled
Unit Tests: Proxy DB Operations / schema-migration (push) Has been cancelled
Unit Tests: Proxy DB Operations / proxy-utils (push) Has been cancelled

This commit is contained in:
michelligabriele 2026-05-28 23:24:07 +02:00
parent e2d2b60a3b
commit 4d1c83ffc2
No known key found for this signature in database
2 changed files with 71 additions and 14 deletions

View file

@ -67,6 +67,11 @@ def _redact_choice_content(choice):
# content already covered by the flat-field scrub. Anthropic populates
# "thinking_blocks" / "reasoning_content" (the latter also carries the
# raw "signature" blob); Bedrock converse populates "reasoningContentBlocks".
#
# This is an intentional allowlist: we redact known reasoning-bearing keys
# rather than wiping all of provider_specific_fields (which also carries
# benign provider metadata). Any future provider field that embeds raw
# reasoning or prompt content must be added here to be redacted.
_PROVIDER_SPECIFIC_REASONING_KEYS = (
"reasoning_content",
"thinking_blocks",
@ -160,6 +165,25 @@ def _redact_standard_logging_object(model_call_details: dict):
standard_logging_object["response"] = {"text": redacted_str}
# Input-bearing keys that show up in request-body snapshots
# (proxy_server_request.body and additional_args.complete_input_dict).
# "messages"/"prompt"/"input" are the OpenAI-style payload entry points;
# "system"/"system_prompt"/"instructions" are the provider-native top-level
# system-prompt fields (Anthropic `system`, Responses API `instructions`),
# which carry user content just as the messages do.
def _redact_request_body_dict(body: dict):
"""Scrub the input/system-prompt keys on a materialised request-body dict."""
if "messages" in body:
body["messages"] = [{"role": "user", "content": "redacted-by-litellm"}]
if "prompt" in body:
body["prompt"] = ""
if "input" in body:
body["input"] = ""
for key in ("system", "system_prompt", "instructions"):
if key in body:
body[key] = "redacted-by-litellm"
def _redact_proxy_server_request_body(model_call_details: dict):
"""Redact the input-bearing keys inside the proxy's body snapshot.
@ -179,12 +203,7 @@ def _redact_proxy_server_request_body(model_call_details: dict):
if not isinstance(body, dict):
return
if "messages" in body:
body["messages"] = [{"role": "user", "content": "redacted-by-litellm"}]
if "prompt" in body:
body["prompt"] = ""
if "input" in body:
body["input"] = ""
_redact_request_body_dict(body)
def _redact_additional_args_complete_input_dict(model_call_details: dict):
@ -205,14 +224,7 @@ def _redact_additional_args_complete_input_dict(model_call_details: dict):
if not isinstance(complete_input_dict, dict):
return
if "messages" in complete_input_dict:
complete_input_dict["messages"] = [
{"role": "user", "content": "redacted-by-litellm"}
]
if "prompt" in complete_input_dict:
complete_input_dict["prompt"] = ""
if "input" in complete_input_dict:
complete_input_dict["input"] = ""
_redact_request_body_dict(complete_input_dict)
def _redact_model_response_dict_choices(choices, redacted_str: str):

View file

@ -547,3 +547,48 @@ class TestPerformRedaction:
perform_redaction({"additional_args": {"complete_input_dict": None}}, None)
# additional_args is not a dict
perform_redaction({"additional_args": "not-a-dict"}, None)
def test_redacts_system_prompt_in_proxy_server_request_body(self):
"""The Anthropic-native top-level `system` prompt (and the
`system_prompt` / `instructions` variants) carry user content just
like messages — they must be scrubbed from the proxy body snapshot."""
details = {
"litellm_params": {
"proxy_server_request": {
"body": {
"model": "claude-3-7-sonnet",
"messages": [{"role": "user", "content": "hi"}],
"system": "CANARY_SYSTEM_should_be_redacted",
}
}
},
}
perform_redaction(details, None)
body = details["litellm_params"]["proxy_server_request"]["body"]
assert body["system"] == "redacted-by-litellm"
def test_redacts_system_prompt_in_complete_input_dict(self):
"""Provider-native system-prompt keys (Anthropic `system`, the
`system_prompt` variant, Responses API `instructions`) must be
scrubbed from the wire-format request body too."""
details = {
"additional_args": {
"complete_input_dict": {
"model": "claude-3-7-sonnet",
"system": [
{"type": "text", "text": "CANARY_SYSTEM_should_be_redacted"}
],
"system_prompt": "CANARY_should_be_redacted",
"instructions": "CANARY_should_be_redacted",
}
}
}
perform_redaction(details, None)
cid = details["additional_args"]["complete_input_dict"]
assert cid["system"] == "redacted-by-litellm"
assert cid["system_prompt"] == "redacted-by-litellm"
assert cid["instructions"] == "redacted-by-litellm"