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
This commit is contained in:
yuneng-jiang 2026-10-14 22:16:47 -07:00 • committed by GitHub
parent d6e6c6f26c
commit 9444f53300
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 11 additions and 19 deletions

View file

@ -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

View file

@ -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