fix(proxy): return 401 not 500 on auth failure in master-key-only mode

On a master-key-only proxy (no DATABASE_URL) the optional `prisma`
dependency is never installed. `PrismaDBExceptionHandler`'s classifiers
(`is_database_connection_error`, `is_prisma_data_error`,
`is_database_transport_error`, `is_prisma_engine_internal_error`) did an
unconditional `import prisma` in the method body. These run on every auth
failure via `_user_api_key_auth_builder`, so any missing/invalid key crashed
the classifier with ModuleNotFoundError and surfaced a 500 instead of a 401.

Guard the import once at module load (`PRISMA_AVAILABLE`) and short-circuit
each classifier to False when prisma is unavailable. Behavior is unchanged
when prisma is installed.

Fixes #35457
This commit is contained in:
Sneha Khoreja 2026-08-01 15:10:25 -04:00
parent 2b3070890a
commit 7911305e29
2 changed files with 54 additions and 4 deletions

View file

@ -8,6 +8,16 @@ from litellm.proxy._types import (
)
from litellm.secret_managers.main import str_to_bool
try:
import prisma # optional dependency, only installed when the proxy is generated against a DATABASE_URL
PRISMA_AVAILABLE = True
except ImportError:
# Master-key-only deployments run without a DATABASE_URL and never install
# prisma. The classifiers below must not crash the auth-failure path in that
# mode, so they short-circuit to False when prisma is unavailable.
PRISMA_AVAILABLE = False
# Bounds the __cause__/__context__ walk in is_database_service_unavailable_error_in_chain.
# Real exception chains are a few links deep; the cap also makes the walk cycle-safe.
_MAX_EXCEPTION_CHAIN_DEPTH = 20
@ -47,7 +57,10 @@ class PrismaDBExceptionHandler:
to True so genuine outages that don't match a specific subclass
still trigger the fallback.
"""
import prisma
if not PRISMA_AVAILABLE:
# No DATABASE_URL / prisma not installed: there is no DB layer to
# be unavailable, so this cannot be a DB-connectivity failure.
return False
# Explicit data-layer exclusion: DB IS reachable, fallback must
# NOT fire.
@ -89,7 +102,8 @@ class PrismaDBExceptionHandler:
per-row data rejection has to additionally consult
``is_database_service_unavailable_error`` before acting on a True here.
"""
import prisma
if not PRISMA_AVAILABLE:
return False
return type(e) is prisma.errors.DataError
@ -102,7 +116,8 @@ class PrismaDBExceptionHandler:
Use this for reconnect logic — data-layer errors like UniqueViolationError
mean the DB IS reachable, so reconnecting would be pointless.
"""
import prisma
if not PRISMA_AVAILABLE:
return False
if isinstance(e, DB_CONNECTION_ERROR_TYPES):
return True
@ -154,7 +169,10 @@ class PrismaDBExceptionHandler:
are already classified by type/keyword above, and data-layer ones
(the DB IS reachable) must stay 401.
"""
import prisma
if not PRISMA_AVAILABLE:
# No prisma engine in play, so no prisma-engine-internal error is
# possible.
return False
if isinstance(e, prisma.errors.PrismaError):
return False

View file

@ -426,3 +426,35 @@ def test_handle_db_exception_with_non_db_error():
)
with pytest.raises(litellm.BudgetExceededError):
PrismaDBExceptionHandler.handle_db_exception(regular_error)
# Regression test for https://github.com/BerriAI/litellm/issues/35457
#
# On a master-key-only proxy (no DATABASE_URL) the optional `prisma` dependency
# is never installed. These classifiers are reached on EVERY auth failure via
# `_user_api_key_auth_builder`, so an unconditional `import prisma` inside them
# raised ModuleNotFoundError and surfaced a 500 where a 401 was expected. With
# prisma unavailable they must instead return False (not a DB error) and not
# raise, so auth failures stay 401.
@pytest.mark.parametrize(
"classifier",
[
PrismaDBExceptionHandler.is_database_connection_error,
PrismaDBExceptionHandler.is_prisma_data_error,
PrismaDBExceptionHandler.is_database_transport_error,
PrismaDBExceptionHandler.is_prisma_engine_internal_error,
],
)
def test_classifiers_return_false_when_prisma_unavailable(monkeypatch, classifier):
"""
When prisma is not installed (master-key-only deployment), the classifiers
must return False without raising ModuleNotFoundError.
"""
monkeypatch.setattr(
"litellm.proxy.db.exception_handler.PRISMA_AVAILABLE", False
)
# A plain auth failure carries no prisma types; the classifier must handle
# it without touching the (absent) prisma module.
auth_error = Exception("Authentication Error, invalid api key")
assert classifier(auth_error) is False