mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
fix(masker): bound the credential walk at the generic recursion depth
Failing closed at the sensitive-data masker's depth of 10 turned an ordinary nested tool JSON schema into REDACTED leaves, because a list level costs two depth. The walk now bounds on DEFAULT_MAX_RECURSE_DEPTH, which no real payload reaches, and the masker's own limit is left alone.
This commit is contained in:
parent
3abed5f4c9
commit
86c5159d96
2 changed files with 45 additions and 7 deletions
|
|
@ -3,7 +3,7 @@ from typing import Any, Final
|
|||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH_SENSITIVE_DATA_MASKER
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH, DEFAULT_MAX_RECURSE_DEPTH_SENSITIVE_DATA_MASKER
|
||||
from litellm.litellm_core_utils.secret_redaction import REDACTED
|
||||
|
||||
|
||||
|
|
@ -226,9 +226,10 @@ def redact_credentials_in_payload(data: Mapping[str, object]) -> Mapping[str, ob
|
|||
and non-string secrets are covered too, which is what a payload rendered
|
||||
straight to stdout needs. ``None`` is preserved so an unset credential still
|
||||
reads as unset, and lists and tuples are rebuilt element by element so a
|
||||
credential nested inside one is caught as well. A container sitting at the
|
||||
recursion limit is replaced wholesale rather than passed through, so nesting a
|
||||
payload deeper than the limit hides it instead of exposing it.
|
||||
credential nested inside one is caught as well. The walk is bounded only to stop
|
||||
runaway recursion, and a container sitting at that bound is replaced wholesale
|
||||
rather than passed through, so burying a credential deeper than the walk goes
|
||||
hides it instead of exposing it.
|
||||
"""
|
||||
return _redact_mapping(data, 0)
|
||||
|
||||
|
|
@ -242,7 +243,7 @@ def _redact_entry(key: str, value: object, depth: int) -> object:
|
|||
return REDACTED
|
||||
if not isinstance(value, (Mapping, list, tuple)):
|
||||
return value
|
||||
if depth >= DEFAULT_MAX_RECURSE_DEPTH_SENSITIVE_DATA_MASKER:
|
||||
if depth >= DEFAULT_MAX_RECURSE_DEPTH:
|
||||
return REDACTED
|
||||
if isinstance(value, Mapping):
|
||||
return _redact_mapping(value, depth + 1)
|
||||
|
|
|
|||
|
|
@ -380,12 +380,12 @@ def test_redact_credentials_in_payload_hides_containers_at_the_recursion_limit(w
|
|||
buries a credential deeper than the limit must get the container hidden rather than handed
|
||||
back verbatim. Nesting through lists costs depth twice as fast as nesting through mappings,
|
||||
so both shapes are pushed well past the limit here."""
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH_SENSITIVE_DATA_MASKER
|
||||
from litellm.constants import DEFAULT_MAX_RECURSE_DEPTH
|
||||
from litellm.litellm_core_utils.sensitive_data_masker import redact_credentials_in_payload
|
||||
|
||||
fake_key = "sk-fake-lit6835-past-the-limit"
|
||||
node = {"api_key": fake_key}
|
||||
for _ in range(2 * DEFAULT_MAX_RECURSE_DEPTH_SENSITIVE_DATA_MASKER + 1):
|
||||
for _ in range(2 * DEFAULT_MAX_RECURSE_DEPTH + 1):
|
||||
node = {"extra_body": node} if wrap == "mapping" else {"providers": [node]}
|
||||
|
||||
result = redact_credentials_in_payload({**node, "max_tokens": 17})
|
||||
|
|
@ -393,3 +393,40 @@ def test_redact_credentials_in_payload_hides_containers_at_the_recursion_limit(w
|
|||
assert fake_key not in str(result)
|
||||
assert "REDACTED" in str(result)
|
||||
assert result["max_tokens"] == 17
|
||||
|
||||
|
||||
def test_redact_credentials_in_payload_leaves_a_realistic_tool_schema_intact():
|
||||
"""The bound must not eat ordinary payloads: a tool whose JSON schema nests an array of
|
||||
objects inside a nested object is what agent traffic looks like, and the verbose line is
|
||||
useless if those leaves come back as REDACTED."""
|
||||
from litellm.litellm_core_utils.sensitive_data_masker import redact_credentials_in_payload
|
||||
|
||||
tool = {
|
||||
"type": "function",
|
||||
"function": {
|
||||
"name": "search_orders",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"filters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {"sku": {"type": "string"}, "qty": {"type": "integer"}},
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
result = redact_credentials_in_payload({"model": "gpt-4o-mini", "tools": [tool], "api_key": "sk-fake-lit6835"})
|
||||
|
||||
assert "REDACTED" not in str(result["tools"])
|
||||
assert result["tools"][0] == tool
|
||||
assert result["api_key"] == "REDACTED"
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue