mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-13 23:11:40 +00:00
fix(redaction): stop redacting the raw call details so post-call scanning still sees tool arguments
Logging.post_call runs redaction before async_post_mcp_tool_call_hook, and that hook is what post_mcp_call guardrails scan. Redacting mcp_tool_call_metadata on the shared model_call_details therefore handed the scanner the placeholder instead of the arguments the caller actually sent, so enabling message redaction silently disabled tool-argument inspection. The standard logging payload is still redacted, which is what integrations export. Enforcement reads the originals again.
This commit is contained in:
parent
71b09960e9
commit
424ee6913f
2 changed files with 19 additions and 22 deletions
|
|
@ -177,22 +177,6 @@ def redacted_mcp_tool_call_metadata(metadata: object, redacted_str: str) -> obje
|
|||
return {**metadata, "mcp_tool_call_metadata": redacted_call}
|
||||
|
||||
|
||||
def _redact_mcp_tool_call_in_place(model_call_details: dict) -> None:
|
||||
"""Redact the raw `mcp_tool_call_metadata` the MCP server stamped onto the
|
||||
call details, so callbacks reading it off `kwargs` see the same redaction
|
||||
the standard logging payload gets.
|
||||
"""
|
||||
mcp_tool_call: Final = model_call_details.get("mcp_tool_call_metadata")
|
||||
if not isinstance(mcp_tool_call, dict):
|
||||
return
|
||||
|
||||
for key in ("arguments", "result"):
|
||||
if mcp_tool_call.get(key) is not None:
|
||||
# mutable-ok: redacting the shared call details in place is the point; a copy would leave
|
||||
# callbacks that read mcp_tool_call_metadata off kwargs holding the unredacted values
|
||||
mcp_tool_call[key] = "redacted-by-litellm"
|
||||
|
||||
|
||||
def _redact_standard_logging_object(model_call_details: dict):
|
||||
"""Redact messages and response inside standard_logging_object if present."""
|
||||
standard_logging_object: Final = model_call_details.get("standard_logging_object")
|
||||
|
|
@ -278,7 +262,6 @@ def perform_redaction(model_call_details: dict, result, redact_streaming_respons
|
|||
model_call_details["messages"] = [{"role": "user", "content": "redacted-by-litellm"}]
|
||||
model_call_details["prompt"] = ""
|
||||
model_call_details["input"] = ""
|
||||
_redact_mcp_tool_call_in_place(model_call_details)
|
||||
_redact_standard_logging_object(model_call_details)
|
||||
redact_vertex_ai_metadata_from_litellm_params(model_call_details)
|
||||
|
||||
|
|
|
|||
|
|
@ -778,7 +778,11 @@ class TestRedactMcpToolCallMetadata:
|
|||
|
||||
assert redacted_mcp_tool_call_metadata(metadata, "redacted-by-litellm") is metadata
|
||||
|
||||
def test_global_redaction_also_strips_the_raw_call_details_copy(self):
|
||||
def test_global_redaction_leaves_the_raw_call_details_for_post_call_scanning(self):
|
||||
"""`Logging.post_call` redacts before `async_post_mcp_tool_call_hook` runs, and that
|
||||
hook is what post_mcp_call guardrails scan. Redacting the shared call details there
|
||||
would hand the scanner a placeholder instead of the arguments the user actually sent,
|
||||
so enforcement must keep reading the originals."""
|
||||
from litellm.litellm_core_utils.redact_messages import perform_redaction
|
||||
|
||||
model_call_details = {
|
||||
|
|
@ -788,15 +792,25 @@ class TestRedactMcpToolCallMetadata:
|
|||
"arguments": {"city": "TOPSECRETCITY"},
|
||||
"result": {"text": "sensitive"},
|
||||
},
|
||||
"standard_logging_object": {"messages": [], "metadata": {}},
|
||||
"standard_logging_object": {
|
||||
"messages": [],
|
||||
"metadata": {
|
||||
"mcp_tool_call_metadata": {
|
||||
"name": "get_weather",
|
||||
"arguments": {"city": "TOPSECRETCITY"},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
perform_redaction(model_call_details, result=None)
|
||||
|
||||
raw = model_call_details["mcp_tool_call_metadata"]
|
||||
assert raw["arguments"] == "redacted-by-litellm"
|
||||
assert raw["result"] == "redacted-by-litellm"
|
||||
assert raw["name"] == "get_weather"
|
||||
assert raw["arguments"] == {"city": "TOPSECRETCITY"}
|
||||
assert raw["result"] == {"text": "sensitive"}
|
||||
|
||||
exported = model_call_details["standard_logging_object"]["metadata"]["mcp_tool_call_metadata"]
|
||||
assert exported["arguments"] == "redacted-by-litellm"
|
||||
|
||||
def test_payload_without_metadata_does_not_gain_a_metadata_key(self):
|
||||
"""The callback-logs replay endpoint seeds a payload from an unvalidated
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue