From b7151d9b950b016706fb1d9835911f1c818377fa Mon Sep 17 00:00:00 2001 From: Avani-prajapati Date: Wed, 20 May 2026 18:03:22 +0530 Subject: [PATCH] fix(logging): scrub non-primitive tuple args (e.g. dicts) passed via %s (CWE-312) Previously, only string elements in a log record's tuple args were scrubbed. A dict like {"api_key": "sk-..."} passed as `logger.debug("cfg: %s", config)` would reach any custom handler attached to LiteLLM loggers untouched. Now any non-primitive tuple element (i.e. not bool/int/float/None) is converted to its string repr and scrubbed before handlers see the record, closing the parameterised-log bypass identified by Veria AI. Co-Authored-By: Claude Sonnet 4.6 --- litellm/_logging.py | 6 +++++- tests/test_litellm/test_credential_scrubber.py | 9 ++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/litellm/_logging.py b/litellm/_logging.py index 09c3b45d681..9cadbb72a61 100644 --- a/litellm/_logging.py +++ b/litellm/_logging.py @@ -478,7 +478,11 @@ class CredentialScrubberFilter(logging.Filter): } elif isinstance(record.args, tuple): record.args = tuple( - _scrub_secrets(str(a)) if isinstance(a, str) else a + ( + _scrub_secrets(str(a)) + if not isinstance(a, (bool, int, float, type(None))) + else a + ) for a in record.args ) if record.exc_info and record.exc_info[1] is not None: diff --git a/tests/test_litellm/test_credential_scrubber.py b/tests/test_litellm/test_credential_scrubber.py index 92ee04504a6..c8cdf2e2a29 100644 --- a/tests/test_litellm/test_credential_scrubber.py +++ b/tests/test_litellm/test_credential_scrubber.py @@ -113,7 +113,13 @@ class TestCredentialScrubberFilter: f = CredentialScrubberFilter() record = self._make_record( - "vals=%s %s %s", (42, None, "api_key=sk-secret123456789") + "vals=%s %s %s %s", + ( + 42, + None, + "api_key=sk-secret123456789", + {"api_key": "sk-dictval12345678901"}, + ), ) f.filter(record) args = record.args @@ -121,6 +127,7 @@ class TestCredentialScrubberFilter: assert args[0] == 42 assert args[1] is None assert "sk-secret123456789" not in str(args[2]) + assert "sk-dictval12345678901" not in str(args[3]) def test_no_args_no_crash(self): # Branch: record.args is falsy (empty tuple)