From 7911305e29e774e740aba598e06ad953cb9df9eb Mon Sep 17 00:00:00 2001 From: Sneha Khoreja Date: Sat, 1 Aug 2026 15:10:25 -0400 Subject: [PATCH 1/2] 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 --- litellm/proxy/db/exception_handler.py | 26 ++++++++++++--- .../proxy/db/test_exception_handler.py | 32 +++++++++++++++++++ 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/db/exception_handler.py b/litellm/proxy/db/exception_handler.py index e4c565e4464..feea8f86a3d 100644 --- a/litellm/proxy/db/exception_handler.py +++ b/litellm/proxy/db/exception_handler.py @@ -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 diff --git a/tests/test_litellm/proxy/db/test_exception_handler.py b/tests/test_litellm/proxy/db/test_exception_handler.py index 23099177812..8e96c9c6337 100644 --- a/tests/test_litellm/proxy/db/test_exception_handler.py +++ b/tests/test_litellm/proxy/db/test_exception_handler.py @@ -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 From d176454850a13ae9ac0654920bf49ea4936f379f Mon Sep 17 00:00:00 2001 From: Sneha Khoreja Date: Fri, 14 Aug 2026 12:30:59 -0400 Subject: [PATCH 2/2] fix(proxy): assign PRISMA_AVAILABLE once to satisfy reportConstantRedefinition budget --- litellm/proxy/db/exception_handler.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/litellm/proxy/db/exception_handler.py b/litellm/proxy/db/exception_handler.py index 1def33a12ef..72f22b65ec5 100644 --- a/litellm/proxy/db/exception_handler.py +++ b/litellm/proxy/db/exception_handler.py @@ -12,12 +12,17 @@ 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 + _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 + _prisma_available = False + +# Assigned once so the ALL_CAPS name is not redefined across the try/except +# branches; the module-level ``import prisma`` above is what the classifiers +# below dereference (``prisma.errors.*``) when this flag is True. +PRISMA_AVAILABLE = _prisma_available # 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.