fix(proxy): fail closed when fallback authorization lookup errors

A non-ProxyException from the team, project or access-group lookup used to
escape the fallback loop and replace the provider's error. Treat it as a
denial and log it. Also drop the unrelated reformatting of test_router.py
and test_fallback_event_handlers.py so both diffs are additions only.
This commit is contained in:
ryan-crabbe-berri 2026-08-27 15:13:24 -07:00
parent 3ea501430b
commit d18bfe176e
4 changed files with 632 additions and 239 deletions

View file

@ -12,6 +12,7 @@ from typing import Final
from pydantic import BaseModel, ValidationError
from litellm._logging import verbose_proxy_logger
from litellm.proxy._types import ProxyException, UserAPIKeyAuth
from litellm.proxy.auth.auth_checks import can_key_call_resolved_model
from litellm.router import Router
@ -31,6 +32,9 @@ async def is_model_authorized_for_token(*, model: str, valid_token: UserAPIKeyAu
)
except ProxyException:
return False
except Exception as e: # noqa: BLE001 # fail closed: a lookup failure must neither run the fallback nor replace the provider error
verbose_proxy_logger.warning("Skipping fallback to model=%s: authorization lookup failed: %s", model, e)
return False
return True

View file

@ -38,6 +38,23 @@ async def test_is_model_authorized_for_token_follows_the_key_access_groups():
assert await is_model_authorized_for_token(model="secret-model", valid_token=token, llm_router=router) is False
class _RouterWithBrokenAccessGroupLookup(Router):
def get_model_access_groups(self, *args, **kwargs):
raise RuntimeError("access group store unavailable")
@pytest.mark.asyncio
async def test_is_model_authorized_for_token_fails_closed_when_the_lookup_breaks():
router = _RouterWithBrokenAccessGroupLookup(model_list=_router().model_list)
assert (
await is_model_authorized_for_token(
model="open-model", valid_token=_key_limited_to("open-group"), llm_router=router
)
is False
)
@pytest.mark.asyncio
@pytest.mark.parametrize("metadata_field", ["metadata", "litellm_metadata"])
async def test_router_fallback_access_check_authorizes_the_key_carried_in_request_metadata(metadata_field: str):

View file

@ -573,7 +573,9 @@ async def test_run_async_fallback_keeps_a_request_override_distinct_from_the_bar
with pytest.raises(RuntimeError, match="fallback model also failed"):
await run_async_fallback(
litellm_router=router,
fallback_model_group=[{"model": "already-attempted", "messages": [{"role": "user", "content": "shorter"}]}],
fallback_model_group=[
{"model": "already-attempted", "messages": [{"role": "user", "content": "shorter"}]}
],
original_model_group="primary-model",
original_exception=RuntimeError("original failed"),
max_fallbacks=3,

File diff suppressed because it is too large Load diff