From 9444f5330044e86cef4873ae7076e0c161f42def Mon Sep 17 00:00:00 2001 From: yuneng-jiang Date: Wed, 14 Oct 2026 22:16:47 -0700 Subject: [PATCH] fix(proxy): store list-valued audit log changes as JSON text (#46555) LiteLLM_AuditLogs parses before_value and updated_values as JSON but only re-serialized dicts, so a list change reached Prisma as a Python list and the row write failed. Every deployment warning ignore, snooze and restore hit this. Mask and re-serialize any JSON value instead --- litellm/proxy/_types.py | 21 ++------------------- tests/unit/proxy/test__types.py | 9 +++++++++ 2 files changed, 11 insertions(+), 19 deletions(-) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 4f581407cf6..a5cfdfca0cf 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -3364,26 +3364,9 @@ class LiteLLM_AuditLogs(LiteLLMPydanticObjectBase): masker: Final = SensitiveDataMasker(sensitive_patterns={"key"}) if self.before_value is not None: - json_before_value: dict | None = None - if isinstance(self.before_value, str): - json_before_value = json.loads(self.before_value) - elif isinstance(self.before_value, dict): - json_before_value = self.before_value - - if json_before_value is not None: - json_before_value = masker.mask_dict(json_before_value) - self.before_value = json.dumps(json_before_value, default=str) - + self.before_value = json.dumps(masker.mask(self.before_value), default=str) if self.updated_values is not None: - json_updated_values: dict | None = None - if isinstance(self.updated_values, str): - json_updated_values = json.loads(self.updated_values) - elif isinstance(self.updated_values, dict): - json_updated_values = self.updated_values - - if json_updated_values is not None: - json_updated_values = masker.mask_dict(json_updated_values) - self.updated_values = json.dumps(json_updated_values, default=str) + self.updated_values = json.dumps(masker.mask(self.updated_values), default=str) return self diff --git a/tests/unit/proxy/test__types.py b/tests/unit/proxy/test__types.py index 50a4c3a6ca7..5bb55f63e64 100644 --- a/tests/unit/proxy/test__types.py +++ b/tests/unit/proxy/test__types.py @@ -260,6 +260,15 @@ def test_an_audit_log_keeps_the_non_secret_fields_it_recorded(field): } +@pytest.mark.parametrize("field", ("before_value", "updated_values")) +def test_an_audit_log_stores_a_list_of_changes_as_json_text_with_keys_masked(field): + sent = [{"id": "no_redis", "key": SECRET}, {"id": "detailed_debug"}] + + log = _audit_log(**{field: json.dumps(sent)}) + + assert json.loads(getattr(log, field)) == [{"id": "no_redis", "key": SECRET_MASKED}, {"id": "detailed_debug"}] + + @pytest.mark.parametrize("field", ("before_value", "updated_values")) def test_an_audit_log_leaves_a_change_it_has_no_record_of_alone(field): assert getattr(_audit_log(**{field: None}), field) is None