From 4d1c83ffc21adb11f41ec175bbd95bdc7762019b Mon Sep 17 00:00:00 2001 From: michelligabriele Date: Thu, 28 May 2026 23:24:07 +0200 Subject: [PATCH] fix(logging): redact native system-prompt keys in request-body snapshots --- litellm/litellm_core_utils/redact_messages.py | 40 +++++++++++------ .../test_redact_messages.py | 45 +++++++++++++++++++ 2 files changed, 71 insertions(+), 14 deletions(-) diff --git a/litellm/litellm_core_utils/redact_messages.py b/litellm/litellm_core_utils/redact_messages.py index ada12c5c27d..25a2ce6672c 100644 --- a/litellm/litellm_core_utils/redact_messages.py +++ b/litellm/litellm_core_utils/redact_messages.py @@ -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): diff --git a/tests/test_litellm/litellm_core_utils/test_redact_messages.py b/tests/test_litellm/litellm_core_utils/test_redact_messages.py index 599b1b795c7..63bfb9fa446 100644 --- a/tests/test_litellm/litellm_core_utils/test_redact_messages.py +++ b/tests/test_litellm/litellm_core_utils/test_redact_messages.py @@ -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"