From 494d2166fa57569b2932784625a59240b4f37015 Mon Sep 17 00:00:00 2001 From: shivam Date: Thu, 23 Jul 2026 01:11:49 +0000 Subject: [PATCH] fix(proxy): stop logging raw API key in AuditLog on key update The /key/update audit hook stored the raw request key in object_id and updated_values, unlike the create/rotate/delete hooks which reference the hashed token. Use existing_key_row.token for object_id and drop the raw key from updated_values so the AuditLog never persists the plaintext sk- value. Fixes #31620 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- .../proxy/hooks/key_management_event_hooks.py | 6 +-- .../hooks/test_key_management_event_hooks.py | 53 +++++++++++++++++++ 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/hooks/key_management_event_hooks.py b/litellm/proxy/hooks/key_management_event_hooks.py index ebac86c022d..ad99ea24412 100644 --- a/litellm/proxy/hooks/key_management_event_hooks.py +++ b/litellm/proxy/hooks/key_management_event_hooks.py @@ -88,7 +88,7 @@ class KeyManagementEventHooks: @staticmethod async def async_key_updated_hook( data: UpdateKeyRequest, - existing_key_row: Any, + existing_key_row: LiteLLM_VerificationToken, response: Any, user_api_key_dict: UserAPIKeyAuth, litellm_changed_by: Optional[str] = None, @@ -107,7 +107,7 @@ class KeyManagementEventHooks: # Enterprise Feature - Audit Logging. Enable with litellm.store_audit_logs = True if litellm.store_audit_logs is True: - _updated_values = json.dumps(data.json(exclude_none=True), default=str) + _updated_values = json.dumps(data.json(exclude_none=True, exclude={"key"}), default=str) _before_value = existing_key_row.json(exclude_none=True) _before_value = json.dumps(_before_value, default=str) @@ -124,7 +124,7 @@ class KeyManagementEventHooks: ), changed_by_api_key=user_api_key_dict.api_key, table_name=LitellmTableNames.KEY_TABLE_NAME, - object_id=data.key, + object_id=existing_key_row.token or "", action="updated", updated_values=_updated_values, before_value=_before_value, diff --git a/tests/test_litellm/proxy/hooks/test_key_management_event_hooks.py b/tests/test_litellm/proxy/hooks/test_key_management_event_hooks.py index 49c1438154f..7fd26bb07bd 100644 --- a/tests/test_litellm/proxy/hooks/test_key_management_event_hooks.py +++ b/tests/test_litellm/proxy/hooks/test_key_management_event_hooks.py @@ -155,6 +155,59 @@ class TestKeyManagementEventHooksIndependentOperations: assert email_called["called"] is True +class TestKeyUpdateAuditLogDoesNotLeakRawKey: + """Regression tests for issue #31620 - raw API key leaked in AuditLog.""" + + @pytest.mark.asyncio + async def test_updated_hook_uses_hashed_token_and_hides_raw_key(self): + """ + /key/update must reference the key by its hashed token in the audit log, + never the raw sk- value, in either object_id or updated_values. + """ + import asyncio + + from litellm.proxy._types import ( + LiteLLM_VerificationToken, + UpdateKeyRequest, + UserAPIKeyAuth, + ) + + raw_key = "sk-super-secret-raw-key-should-never-be-logged" + hashed_token = "a" * 64 + + data = UpdateKeyRequest(key=raw_key, max_budget=2000.0) + existing_key_row = LiteLLM_VerificationToken( + token=hashed_token, + key_name="sk-...oged", + max_budget=100.0, + ) + + captured = {} + + async def fake_create_audit_log_for_update(request_data): + captured["log"] = request_data + + with ( + patch( + "litellm.proxy.management_helpers.audit_logs.create_audit_log_for_update", + side_effect=fake_create_audit_log_for_update, + ), + patch("litellm.store_audit_logs", True), + ): + await KeyManagementEventHooks.async_key_updated_hook( + data=data, + existing_key_row=existing_key_row, + response=existing_key_row, + user_api_key_dict=UserAPIKeyAuth(api_key="hashed-admin", user_id="default_user_id"), + ) + await asyncio.sleep(0.05) + + log = captured["log"] + assert log.object_id == hashed_token + assert raw_key not in (log.object_id or "") + assert raw_key not in (log.updated_values or "") + + class TestRotateVirtualKeyInSecretManager: """Tests for _rotate_virtual_key_in_secret_manager with team_id support."""