From 782d1ebb4094f8bea7824b53b4b2a28eb7f69d7e Mon Sep 17 00:00:00 2001 From: yassin Date: Tue, 25 Aug 2026 20:21:51 +0000 Subject: [PATCH] fix(auth): honour cached CLI session revocations before checking DB availability Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/auth/cli_session_registry.py | 16 ++++++++++------ .../proxy/auth/test_cli_session_registry.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/litellm/proxy/auth/cli_session_registry.py b/litellm/proxy/auth/cli_session_registry.py index 4c6cc6e534e..1e48a52b921 100644 --- a/litellm/proxy/auth/cli_session_registry.py +++ b/litellm/proxy/auth/cli_session_registry.py @@ -105,20 +105,24 @@ async def is_cli_session_revoked( interval per replica rather than one per request. That TTL is also the bound on how long a revocation takes to reach a replica that did not serve the revoke. - A lookup that cannot reach the database follows the proxy-wide + A proxy running without a database has no registry, so nothing can have been + revoked; those sessions authenticate as they always have. A lookup that cannot + reach a configured database follows the proxy-wide ``allow_requests_on_db_unavailable`` posture rather than inventing its own: an - operator who opted into serving during an outage keeps serving CLI sessions, - and one who did not gets the same failure every other DB-backed auth read gives. + operator who opted into serving during an outage keeps serving CLI sessions, and + one who did not gets the same failure every other DB-backed auth read gives. The + cache is consulted first either way, so a revocation this replica already knows + about is refused regardless of DB health. """ - if prisma_client is None: - return False - session_id: Final = cli_session_id(session_token) cache_key: Final = _revocation_cache_key(session_id) cached: Final = await user_api_key_cache.async_get_cache(key=cache_key) if cached is not None: return bool(cached) + if prisma_client is None: + return False + try: session: Final = await _get_cli_session(prisma_client=prisma_client, session_id=session_id) except Exception as e: # noqa: BLE001 # handle_db_exception takes any exception and re-raises what it does not recognise diff --git a/tests/test_litellm/proxy/auth/test_cli_session_registry.py b/tests/test_litellm/proxy/auth/test_cli_session_registry.py index d7ba4012dd7..c738606509e 100644 --- a/tests/test_litellm/proxy/auth/test_cli_session_registry.py +++ b/tests/test_litellm/proxy/auth/test_cli_session_registry.py @@ -297,12 +297,25 @@ async def test_recorded_session_is_keyed_by_the_hash_not_the_token(): @pytest.mark.asyncio async def test_no_db_connection_does_not_refuse_the_session(): + """A proxy running without a database has no registry, so nothing can have been + revoked and the session keeps authenticating.""" assert ( await is_cli_session_revoked(session_token=SESSION_TOKEN, prisma_client=None, user_api_key_cache=_cache()) is False ) +@pytest.mark.asyncio +async def test_cached_revocation_is_refused_even_without_a_db_client(): + cache = _cache() + await cache.async_set_cache(key=f"cli_session_revoked:{hash_token(SESSION_TOKEN)}", value=True, ttl=60) + + assert ( + await is_cli_session_revoked(session_token=SESSION_TOKEN, prisma_client=None, user_api_key_cache=cache) + is True + ) + + class UnreachableCLISessionTable(FakeCLISessionTable): async def find_unique(self, where): raise EngineConnectionError("Could not connect to the query engine")