fix(proxy): never log raw virtual keys in key insertion debug output (#33268)

* fix(proxy): never log raw virtual keys in key insertion debug output

* fix(proxy): tolerate None token in insert_data debug log redaction
This commit is contained in:
yucheng-berri 2026-07-14 16:32:26 -07:00 • committed by GitHub
parent 668df9494a
commit f31dacbcd4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 89 additions and 3 deletions

View file

@ -42,7 +42,7 @@ from litellm.proxy._experimental.mcp_server.db import (
rotate_mcp_user_env_vars_master_key,
)
from litellm.proxy._types import *
from litellm.proxy._types import LiteLLM_VerificationToken
from litellm.proxy._types import LiteLLM_VerificationToken, hash_token
from litellm.proxy.auth.auth_checks import (
_delete_cache_key_object,
can_team_access_model,
@ -3777,7 +3777,10 @@ async def generate_key_helper_fn(
return user_data
## CREATE KEY
verbose_proxy_logger.debug("prisma_client: Creating Key= %s", key_data)
verbose_proxy_logger.debug(
"prisma_client: Creating Key= %s",
{**key_data, "token": hash_token(token=token)},
)
create_key_response = await prisma_client.insert_data(data=key_data, table_name="key")
key_data["token_id"] = getattr(create_key_response, "token", None)

View file

@ -3592,7 +3592,10 @@ class PrismaClient:
"""
start_time = time.time()
try:
verbose_proxy_logger.debug("PrismaClient: insert_data: %s", data)
verbose_proxy_logger.debug(
"PrismaClient: insert_data: %s",
{**data, "token": self.hash_token(token=data["token"])} if data.get("token") is not None else data,
)
if table_name == "key":
token = data["token"]
hashed_token = self.hash_token(token=token)

View file

@ -529,6 +529,56 @@ async def test_key_generation_with_object_permission(monkeypatch):
assert key_insert_calls[0]["data"].get("object_permission_id") == "objperm123"
@pytest.mark.asyncio
async def test_generate_key_debug_log_never_contains_raw_token(monkeypatch, caplog):
"""Regression for LIT-4356: /key/generate must never emit the raw virtual key
to a logger, even for short keys that bypass the regex-based
SecretRedactionFilter."""
import hashlib
import logging
mock_prisma_client = AsyncMock()
mock_prisma_client.jsonify_object = lambda data: data
mock_prisma_client.db = MagicMock()
async def _insert_data_side_effect(*args, **kwargs):
if kwargs.get("table_name") == "user":
return MagicMock(models=[], spend=0)
return MagicMock(
token="hashed_token_456",
litellm_budget_table=None,
object_permission=None,
)
mock_prisma_client.insert_data = AsyncMock(side_effect=_insert_data_side_effect)
monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client)
monkeypatch.setattr(
"litellm.proxy.management_endpoints.key_management_endpoints.get_ui_settings_cached",
AsyncMock(return_value={}),
)
from litellm.proxy._types import GenerateKeyRequest, LitellmUserRoles
from litellm.proxy.auth.user_api_key_auth import UserAPIKeyAuth
from litellm.proxy.management_endpoints.key_management_endpoints import (
generate_key_fn,
)
raw_key = "sk-short-secret"
with caplog.at_level(logging.DEBUG, logger="LiteLLM Proxy"):
await generate_key_fn(
data=GenerateKeyRequest(key=raw_key),
user_api_key_dict=UserAPIKeyAuth(
user_role=LitellmUserRoles.PROXY_ADMIN,
api_key="sk-1234",
user_id="user-1",
),
)
log_text = "\n".join(record.getMessage() for record in caplog.records)
assert raw_key not in log_text
assert hashlib.sha256(raw_key.encode()).hexdigest() in log_text
@pytest.mark.asyncio
@pytest.mark.parametrize(
"field,request_kwargs,expected_in_error",

View file

@ -10,6 +10,7 @@ from __future__ import annotations
import hashlib
import json
import logging
from types import SimpleNamespace
from typing import Any
from unittest.mock import AsyncMock, MagicMock
@ -103,6 +104,35 @@ async def test_insert_data_user_organization_fk_raises_400(
assert raised.status_code == 400
@pytest.mark.asyncio
async def test_insert_data_debug_log_hashes_token(
prisma_client: PrismaClient, caplog: pytest.LogCaptureFixture
) -> None:
"""Regression for LIT-4356: the raw virtual key must never reach a logger,
even for short/nonstandard key formats that bypass the regex-based
SecretRedactionFilter."""
token = "sk-short-secret"
expected_hash = hashlib.sha256(token.encode()).hexdigest()
prisma_client.db.litellm_verificationtoken.upsert = AsyncMock(return_value=SimpleNamespace(token=expected_hash))
with caplog.at_level(logging.DEBUG, logger="LiteLLM Proxy"):
await prisma_client.insert_data(data={"token": token, "key_alias": "redaction-repro"}, table_name="key")
log_text = "\n".join(record.getMessage() for record in caplog.records)
assert token not in log_text
assert expected_hash in log_text
@pytest.mark.asyncio
async def test_insert_data_debug_log_tolerates_none_token(
prisma_client: PrismaClient, caplog: pytest.LogCaptureFixture
) -> None:
"""A None token must not crash the redacting debug log added for LIT-4356."""
prisma_client.db.litellm_usertable.upsert = AsyncMock(return_value=SimpleNamespace(user_id="u1"))
with caplog.at_level(logging.DEBUG, logger="LiteLLM Proxy"):
result = await prisma_client.insert_data(data={"user_id": "u1", "token": None}, table_name="user")
assert result.user_id == "u1"
assert any("insert_data" in record.getMessage() for record in caplog.records)
@pytest.mark.asyncio
async def test_insert_data_logs_and_raises_generic_error(
prisma_client: PrismaClient,