mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-07 08:26:10 +00:00
fix(masker): hide containers at the redaction depth limit instead of passing them through
This commit is contained in:
parent
912572bfa5
commit
3abed5f4c9
2 changed files with 29 additions and 8 deletions
|
|
@ -226,30 +226,30 @@ 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.
|
||||
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.
|
||||
"""
|
||||
return _redact_mapping(data, 0)
|
||||
|
||||
|
||||
def _redact_mapping(data: Mapping[str, object], depth: int) -> Mapping[str, object]:
|
||||
if depth >= DEFAULT_MAX_RECURSE_DEPTH_SENSITIVE_DATA_MASKER:
|
||||
return data
|
||||
return {key: _redact_entry(key, value, depth) for key, value in data.items()}
|
||||
|
||||
|
||||
def _redact_entry(key: str, value: object, depth: int) -> object:
|
||||
if value is not None and _default_masker.is_sensitive_key(key):
|
||||
return REDACTED
|
||||
if not isinstance(value, (Mapping, list, tuple)):
|
||||
return value
|
||||
if depth >= DEFAULT_MAX_RECURSE_DEPTH_SENSITIVE_DATA_MASKER:
|
||||
return REDACTED
|
||||
if isinstance(value, Mapping):
|
||||
return _redact_mapping(value, depth + 1)
|
||||
if isinstance(value, (list, tuple)):
|
||||
return _redact_sequence(value, depth + 1)
|
||||
return value
|
||||
return _redact_sequence(value, depth + 1)
|
||||
|
||||
|
||||
def _redact_sequence(values: Sequence[object], depth: int) -> Sequence[object]:
|
||||
if depth >= DEFAULT_MAX_RECURSE_DEPTH_SENSITIVE_DATA_MASKER:
|
||||
return values
|
||||
redacted: Final = tuple(_redact_entry("", item, depth) for item in values)
|
||||
return redacted if isinstance(values, tuple) else list(redacted)
|
||||
|
||||
|
|
|
|||
|
|
@ -372,3 +372,24 @@ def test_redact_credentials_in_payload_reaches_credentials_nested_in_sequences()
|
|||
assert result["metadata"]["upstreams"][0]["aws_secret_access_key"] == "REDACTED"
|
||||
assert isinstance(result["metadata"]["upstreams"], tuple)
|
||||
assert result["messages"] == [{"role": "user", "content": "hello"}]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("wrap", ["mapping", "sequence"])
|
||||
def test_redact_credentials_in_payload_hides_containers_at_the_recursion_limit(wrap):
|
||||
"""The recursion limit exists to bound the walk, not to grant an exemption, so a caller who
|
||||
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.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):
|
||||
node = {"extra_body": node} if wrap == "mapping" else {"providers": [node]}
|
||||
|
||||
result = redact_credentials_in_payload({**node, "max_tokens": 17})
|
||||
|
||||
assert fake_key not in str(result)
|
||||
assert "REDACTED" in str(result)
|
||||
assert result["max_tokens"] == 17
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue