fix(proxy): evict already blocked keys again when POST /key/revoke sees them
Some checks are pending
Cost map pins recheck / rerun-cost-map-guard (push) Waiting to run
LiteLLM Rust DB / db-queries (push) Waiting to run
LiteLLM Rust / rust-lint (push) Waiting to run
LiteLLM Rust / rust-test (push) Waiting to run
LiteLLM Rust / rust-wheel (push) Waiting to run

Cache eviction is best-effort, so a report could save the block while another worker kept a cached copy of the key. A later report skipped already blocked keys and never retried the eviction. Every matched key is now evicted on every report, and only newly blocked keys get the database update, audit row and warning
This commit is contained in:
Yuneng Jiang 2026-11-03 15:25:08 -08:00
parent ed450c507c
commit 8e89aa3c4d
No known key found for this signature in database
2 changed files with 24 additions and 6 deletions

View file

@ -81,16 +81,17 @@ async def _block_live_keys(
) -> None:
from litellm.proxy.proxy_server import proxy_logging_obj, user_api_key_cache
newly_blocked: Final = tuple(
(row.token, row) for row in await tokens.find_by_tokens(hashed_tokens) if row.token and row.blocked is not True
)
if not newly_blocked:
return
matched: Final = tuple((row.token, row) for row in await tokens.find_by_tokens(hashed_tokens) if row.token)
newly_blocked: Final = tuple((token, row) for token, row in matched if row.blocked is not True)
blocked_tokens: Final = tuple(token for token, _ in newly_blocked)
await tokens.block_tokens(blocked_tokens)
await delete_cache_key_objects(
hashed_tokens=blocked_tokens, user_api_key_cache=user_api_key_cache, proxy_logging_obj=proxy_logging_obj
hashed_tokens=tuple(token for token, _ in matched),
user_api_key_cache=user_api_key_cache,
proxy_logging_obj=proxy_logging_obj,
)
if not blocked_tokens:
return
if is_audit_logging_enabled():
await audit(tuple(_audit_row(row, token) for token, row in newly_blocked))
verbose_proxy_logger.warning(

View file

@ -233,6 +233,23 @@ def test_an_already_blocked_key_gets_no_second_update_or_audit_row(monkeypatch,
assert audit_rows == []
def test_reporting_an_already_blocked_key_again_evicts_a_stale_cached_copy(monkeypatch, audit_rows, caplog):
table: Final = _install(monkeypatch, {BLOCKED_HASH: _row(BLOCKED_HASH, blocked=True)})
cache: Final = proxy_server.user_api_key_cache
cache.set_cache(key=BLOCKED_HASH, value=UserAPIKeyAuth(token=BLOCKED_HASH, blocked=False).model_dump())
assert cache.get_cache(key=BLOCKED_HASH) is not None
verbose_proxy_logger.addHandler(caplog.handler)
try:
with caplog.at_level(logging.DEBUG, logger=verbose_proxy_logger.name):
assert _revoke(BLOCKED) == ACCEPTED
finally:
verbose_proxy_logger.removeHandler(caplog.handler)
assert cache.get_cache(key=BLOCKED_HASH) is None
assert (table.updates, audit_rows) == ([], [])
assert not any("Blocked" in record.getMessage() for record in caplog.records), caplog.records
@pytest.mark.parametrize(
"authorization",
[f"Bearer {MASTER_KEY}", "Bearer sk-not-a-real-key", "Basic Zm9vOmJhcg==", "garbage"],