fix(router): authorize fusion dependencies

This commit is contained in:
moe-berri 2026-09-03 14:47:11 -07:00
parent 39541f2c92
commit 462cdd6990
2 changed files with 127 additions and 0 deletions

View file

@ -2542,6 +2542,10 @@ class Router:
model=model,
llm_provider="",
)
await self._authorize_fusion_dependencies(
fusion_router=fusion_router,
request_kwargs=kwargs,
)
response = ( # rebind-ok: one mutually exclusive dispatch branch assigns it
await fusion_router.acompletion(
messages=messages,
@ -9463,6 +9467,59 @@ class Router:
search=self._fusion_asearch,
)
async def _authorize_fusion_dependencies(
self,
fusion_router: FusionRouter,
request_kwargs: Mapping[str, object],
) -> None:
"""Apply the originating proxy caller's model access to every hidden call."""
metadata_values: Final = tuple(request_kwargs.get(key) for key in ("litellm_metadata", "metadata"))
raw_user_api_key_auth: Final = next(
(
metadata.get("user_api_key_auth")
for metadata in metadata_values
if isinstance(metadata, Mapping) and metadata.get("user_api_key_auth") is not None
),
None,
)
proxy_auth_required: Final = isinstance(request_kwargs.get("proxy_server_request"), Mapping)
if raw_user_api_key_auth is None and not proxy_auth_required:
return
from litellm.proxy._types import ProxyErrorTypes, ProxyException, UserAPIKeyAuth
from litellm.proxy.auth.auth_checks import can_key_call_resolved_model
try:
user_api_key_auth: Final = (
raw_user_api_key_auth
if isinstance(raw_user_api_key_auth, UserAPIKeyAuth)
else UserAPIKeyAuth.model_validate(raw_user_api_key_auth)
)
except ValidationError as exc:
raise ProxyException(
message="Fusion model authorization context is missing or invalid",
type=ProxyErrorTypes.auth_error,
param="model",
code=403,
) from exc
dependency_models: Final = tuple(
dict.fromkeys(
(
fusion_router.config.outer_model,
*fusion_router.config.panel_models,
fusion_router.config.resolved_analyst_model,
)
)
)
for dependency_model in dependency_models:
await can_key_call_resolved_model(
model=dependency_model,
llm_model_list=self.model_list,
valid_token=user_api_key_auth,
llm_router=self,
)
async def _fusion_asearch( # kwargs-ok: bridge preserves the Router.asearch keyword surface
self,
*,

View file

@ -668,6 +668,76 @@ async def test_router_registers_and_executes_fusion_deployment() -> None:
assert "fusion/test" not in router.fusion_routers
@pytest.mark.asyncio
async def test_proxy_fusion_authorizes_every_hidden_model(monkeypatch: pytest.MonkeyPatch) -> None:
from litellm.proxy._types import UserAPIKeyAuth
from litellm.proxy.auth import auth_checks
model_list = _router_model_list()
model_list.insert(
-1,
{
"model_name": "analyst",
"litellm_params": {"model": "openai/test", "api_key": "fake", "mock_response": "Analysis"},
},
)
model_list[-1]["litellm_params"]["fusion_router_config"]["analyst_model"] = "analyst"
router = Router(model_list=model_list)
authorize = AsyncMock(return_value=None)
monkeypatch.setattr(auth_checks, "can_key_call_resolved_model", authorize)
response = await router.acompletion(
model="fusion/test",
messages=[{"role": "user", "content": "Answer"}],
metadata={"user_api_key_auth": UserAPIKeyAuth(models=["*"])},
proxy_server_request={"body": {"model": "fusion/test"}},
)
assert isinstance(response, ModelResponse)
assert [call.kwargs["model"] for call in authorize.await_args_list] == ["outer", "panel-a", "analyst"]
@pytest.mark.asyncio
async def test_proxy_fusion_denies_hidden_model_before_any_provider_call(monkeypatch: pytest.MonkeyPatch) -> None:
from litellm.proxy._types import ProxyErrorTypes, ProxyException, UserAPIKeyAuth
from litellm.proxy.auth import auth_checks
router = Router(model_list=_router_model_list())
denial = ProxyException(
message="key not allowed to access model",
type=ProxyErrorTypes.key_model_access_denied,
param="model",
code=403,
)
monkeypatch.setattr(auth_checks, "can_key_call_resolved_model", AsyncMock(side_effect=denial))
fusion_completion = AsyncMock()
monkeypatch.setattr(router.fusion_routers["fusion/test"], "acompletion", fusion_completion)
with pytest.raises(ProxyException, match="key not allowed"):
await router.acompletion(
model="fusion/test",
messages=[{"role": "user", "content": "Answer"}],
metadata={"user_api_key_auth": UserAPIKeyAuth(models=["fusion/test"])},
proxy_server_request={"body": {"model": "fusion/test"}},
)
fusion_completion.assert_not_awaited()
@pytest.mark.asyncio
async def test_proxy_fusion_fails_closed_without_authorization_context() -> None:
from litellm.proxy._types import ProxyException
router = Router(model_list=_router_model_list())
with pytest.raises(ProxyException, match="authorization context is missing or invalid"):
await router.acompletion(
model="fusion/test",
messages=[{"role": "user", "content": "Answer"}],
proxy_server_request={"body": {"model": "fusion/test"}},
)
@pytest.mark.asyncio
async def test_router_replays_direct_outer_response_as_an_async_stream() -> None:
router = Router(model_list=_router_model_list())