From 471f51cc4deb5aa2db57bff70c5eac32eaa3bfc3 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Fri, 4 Sep 2026 09:29:09 -0700 Subject: [PATCH] fix(proxy): keep another team's deployment out of /health for keys with no team --- .../health_endpoints/_health_endpoints.py | 4 +- .../health_endpoints/test_health_endpoints.py | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/litellm/proxy/health_endpoints/_health_endpoints.py b/litellm/proxy/health_endpoints/_health_endpoints.py index cc9ad1eb0cb..04388c773a0 100644 --- a/litellm/proxy/health_endpoints/_health_endpoints.py +++ b/litellm/proxy/health_endpoints/_health_endpoints.py @@ -880,7 +880,9 @@ def _health_accessible_model_names( def _caller_may_probe_deployment( deployment: Mapping[str, object], allowed_models: frozenset[str], llm_router: Router | None, team_id: str | None ) -> bool: - """Same deployment visibility rule as request auth: another team's deployment is never in scope.""" + """Same deployment visibility rule as routing: another team's deployment is never in scope, team-less callers included.""" + if not Router._deployment_usable_by_team(deployment, team_id): + return False if llm_router is None: return deployment.get("model_name") in allowed_models model: Final = dict(deployment) diff --git a/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py b/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py index 6e5c807afba..f4ed50fdb5f 100644 --- a/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py +++ b/tests/test_litellm/proxy/health_endpoints/test_health_endpoints.py @@ -2830,6 +2830,21 @@ async def test_health_endpoint_hides_another_teams_deployment_behind_a_shared_ac assert probed == {"id-bedrock"} +@pytest.mark.asyncio +async def test_health_endpoint_hides_team_deployments_from_a_key_with_no_team(): + """ + Routing never serves a team-owned deployment to a caller without a team + (``filter_team_based_models``), so a team-less access-group key must not + probe team-b's deployment with team-b's credentials either. + """ + probed = await _live_probed_model_ids( + _TEAM_MODEL_LIST, + UserAPIKeyAuth(api_key="hashed-test-key", models=["bedrock-group"], team_id=None), + ) + + assert probed == {"id-bedrock"} + + @pytest.mark.asyncio async def test_health_endpoint_shows_a_teams_own_deployment_by_its_public_name(): """ @@ -2868,6 +2883,29 @@ async def test_health_endpoint_hides_another_teams_deployment_on_background_cach assert result["healthy_count"] == 1 +@pytest.mark.asyncio +async def test_health_endpoint_hides_team_deployments_from_a_key_with_no_team_on_background_cache_path(): + from fastapi import Response + + from litellm.proxy.health_endpoints._health_endpoints import health_endpoint + + with _proxy_health_globals( + _TEAM_MODEL_LIST, + _router_for(_TEAM_MODEL_LIST), + use_background_health_checks=True, + health_check_results=_TEAM_CACHED_RESULTS, + ): + result = await health_endpoint( + response=Response(), + user_api_key_dict=UserAPIKeyAuth(api_key="hashed-test-key", models=["bedrock-group"], team_id=None), + model=None, + model_id=None, + ) + + assert [ep["model_id"] for ep in result["healthy_endpoints"]] == ["id-bedrock"] + assert result["healthy_count"] == 1 + + def test_health_test_connection_keeps_error_and_raw_request_through_the_allowlist(monkeypatch): """ The dashboard's Test Connect button reads ``result.error`` and