From c4505baa8577e524f1aeef04c7372d69cb5c37f7 Mon Sep 17 00:00:00 2001 From: milan Date: Tue, 28 Jul 2026 15:01:48 +0000 Subject: [PATCH] fix(proxy): authorize model_group_alias target before routing The request is authorized against the requested model group, so the alias target has to clear model access checks on its own before dispatch. Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/route_llm_request.py | 9 +++++ .../proxy/test_route_llm_request.py | 39 +++++++++++++++++++ 2 files changed, 48 insertions(+) diff --git a/litellm/proxy/route_llm_request.py b/litellm/proxy/route_llm_request.py index 8f3abfae1a7..d6d9b78a670 100644 --- a/litellm/proxy/route_llm_request.py +++ b/litellm/proxy/route_llm_request.py @@ -485,6 +485,15 @@ async def route_request( aliased_model = _resolve_per_request_model_group_alias(override_settings, data.get("model")) if aliased_model is not None: + if user_api_key_dict is not None: + from litellm.proxy.auth.auth_checks import can_key_call_resolved_model + + await can_key_call_resolved_model( + model=aliased_model, + llm_model_list=llm_router.model_list if llm_router is not None else None, + valid_token=user_api_key_dict, + llm_router=llm_router, + ) data["model"] = aliased_model # Use main router with overridden kwargs diff --git a/tests/test_litellm/proxy/test_route_llm_request.py b/tests/test_litellm/proxy/test_route_llm_request.py index 5f8e96933d1..ec597a6454c 100644 --- a/tests/test_litellm/proxy/test_route_llm_request.py +++ b/tests/test_litellm/proxy/test_route_llm_request.py @@ -547,6 +547,45 @@ async def test_route_request_leaves_model_untouched_for_irrelevant_or_malformed_ assert response.choices[0].message.content == "from-group-a" +@pytest.mark.asyncio +async def test_route_request_model_group_alias_target_needs_model_access(): + """ + The request is authorized against the requested model, so an alias pointing at a + model group the key cannot access must be rejected rather than silently served. + """ + from litellm.proxy._types import ProxyException, UserAPIKeyAuth + + router = _router_with_two_model_groups() + key = UserAPIKeyAuth(api_key="sk-restricted", models=["group-a"]) + data = { + "model": "group-a", + "messages": [{"role": "user", "content": "Hello"}], + "router_settings_override": {"model_group_alias": {"group-a": "group-b"}}, + } + + with pytest.raises(ProxyException) as exc_info: + await route_request(data, router, None, "acompletion", user_api_key_dict=key) + + assert "group-b" in exc_info.value.message + + +@pytest.mark.asyncio +async def test_route_request_model_group_alias_applies_when_target_is_allowed(): + from litellm.proxy._types import UserAPIKeyAuth + + router = _router_with_two_model_groups() + key = UserAPIKeyAuth(api_key="sk-allowed", models=["group-a", "group-b"]) + data = { + "model": "group-a", + "messages": [{"role": "user", "content": "Hello"}], + "router_settings_override": {"model_group_alias": {"group-a": "group-b"}}, + } + + response = await (await route_request(data, router, None, "acompletion", user_api_key_dict=key)) + + assert response.choices[0].message.content == "from-group-b" + + def test_mock_testing_kwarg_names_matches_dataclass(): """``_MOCK_TESTING_KWARG_NAMES`` is hardcoded to avoid a cyclic import against ``litellm.types.router``. This test guards against drift —