diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index b9784de5bee..bae8049987c 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -262,9 +262,21 @@ async def _canonical_target_is_allowed( A denial returns False rather than raising, so the request falls through to the same 400 an unresolvable model gets today and the response reveals nothing about the target's existence. + + Absent key context fails CLOSED. Most ``route_request`` callers (image + generation, rerank, moderation, speech, transcription, realtime, Responses + WebSocket) are authenticated but do not currently forward + ``user_api_key_dict``, so treating "no key context" as "allowed" would run + the rewrite with no target authorization at all on exactly those paths. + Declining instead costs those endpoints only the convenience rewrite -- + they behave as they do today, resolution simply never engages -- while + keeping the AND-on-target guarantee unconditional. Threading the key + through those call sites is the follow-up that re-enables resolution for + them; until then this must not be the hole through which the check is + skipped. """ if user_api_key_dict is None: - return True + return False from litellm.proxy.auth.auth_checks import ( can_key_call_resolved_model, # pyright: ignore[reportUnknownVariableType] - auth_checks is partially typed ) diff --git a/tests/test_litellm/router_utils/test_router_canonical_model_resolution.py b/tests/test_litellm/router_utils/test_router_canonical_model_resolution.py index 8953e949b67..7be77dc73d9 100644 --- a/tests/test_litellm/router_utils/test_router_canonical_model_resolution.py +++ b/tests/test_litellm/router_utils/test_router_canonical_model_resolution.py @@ -509,9 +509,17 @@ class TestCanonicalTargetReAuth: assert allowed is False @pytest.mark.asyncio - async def test_no_auth_context_is_allowed(self, anthropic_router: Router): - """No key context (non-proxy Router use) leaves the rewrite unguarded by - key auth, matching the surrounding call path.""" + async def test_absent_auth_context_fails_closed(self, anthropic_router: Router): + """Regression: absent key context must DECLINE the rewrite, not allow it. + + Most route_request callers (image generation, rerank, moderation, + speech, transcription, realtime, Responses WebSocket) are authenticated + but don't currently forward user_api_key_dict. Returning True here + would run the rewrite with no target authorization at all on exactly + those paths -- a key whose allowlist holds only the stale requested + spelling could reach a target it was never granted. Declining costs + those endpoints only the convenience rewrite; the AND-on-target + guarantee stays unconditional.""" from litellm.proxy.route_llm_request import _canonical_target_is_allowed assert ( @@ -520,7 +528,7 @@ class TestCanonicalTargetReAuth: llm_router=anthropic_router, user_api_key_dict=None, ) - is True + is False )