fix(auth): deny model access for teamless keys with all-team-models (#32022)

_enforce_key_and_fallback_model_access and can_key_call_resolved_model
both unconditionally skipped key-level model checks whenever
all-team-models was present, without verifying the key actually belongs
to a team. PR #29746 fixed the listing path (get_key_models) but these
two call-path checks were left untouched, letting teamless keys call
any model via /chat/completions while seeing an empty model list.

Add team_id is not None guard to both bypass conditions so teamless
keys fall through to can_key_call_model, which already correctly
rejects unresolvable all-team-models sentinels

Co-authored-by: mateo <mateo@berri.ai>
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
devin-ai-integration[bot] 2026-07-02 21:34:36 -07:00 committed by GitHub
parent cbe23d6184
commit dfbbda4f19
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 60 additions and 2 deletions

View file

@ -3017,7 +3017,9 @@ async def can_key_call_resolved_model(
)
skip_key_model_check = valid_token.config or (
isinstance(valid_token.models, list) and SpecialModelNames.all_team_models.value in valid_token.models
isinstance(valid_token.models, list)
and SpecialModelNames.all_team_models.value in valid_token.models
and valid_token.team_id is not None
)
if not skip_key_model_check:
await can_key_call_model(

View file

@ -2676,7 +2676,11 @@ async def _enforce_key_and_fallback_model_access(
model_list = config.get("model_list", [])
new_model_list = model_list
verbose_proxy_logger.debug(f"\n new llm router model list {new_model_list}")
elif isinstance(valid_token.models, list) and "all-team-models" in valid_token.models:
elif (
isinstance(valid_token.models, list)
and "all-team-models" in valid_token.models
and valid_token.team_id is not None
):
pass
else:
model = _get_model_from_request_context(

View file

@ -355,6 +355,58 @@ async def test_can_key_call_model_all_team_models_no_team_id_is_denied():
assert exc_info.value.type == ProxyErrorTypes.key_model_access_denied
@pytest.mark.asyncio
async def test_enforce_key_access_teamless_all_team_models_denied():
"""_enforce_key_and_fallback_model_access must not skip key-level
model checks for a teamless key that carries all-team-models."""
from litellm.proxy._types import SpecialModelNames
from litellm.proxy.auth.user_api_key_auth import (
_enforce_key_and_fallback_model_access,
)
valid_token = UserAPIKeyAuth(
api_key="sk-orphan-key",
models=[SpecialModelNames.all_team_models.value],
team_models=[],
)
with pytest.raises(ProxyException) as exc_info:
await _enforce_key_and_fallback_model_access(
valid_token=valid_token,
request_data={"model": "gpt-4o"},
route="/v1/chat/completions",
request=None,
llm_model_list=None,
llm_router=None,
)
assert exc_info.value.type == ProxyErrorTypes.key_model_access_denied
@pytest.mark.asyncio
async def test_can_key_call_resolved_model_teamless_all_team_models_denied():
"""can_key_call_resolved_model must not skip key-level model checks
for a teamless key that carries all-team-models."""
from litellm.proxy._types import SpecialModelNames
from litellm.proxy.auth.auth_checks import can_key_call_resolved_model
valid_token = UserAPIKeyAuth(
api_key="sk-orphan-key",
models=[SpecialModelNames.all_team_models.value],
team_models=[],
)
with pytest.raises(ProxyException) as exc_info:
await can_key_call_resolved_model(
model="gpt-4o",
llm_model_list=None,
valid_token=valid_token,
llm_router=None,
)
assert exc_info.value.type == ProxyErrorTypes.key_model_access_denied
@pytest.mark.asyncio
async def test_can_team_access_model_all_team_models_expands_router_models():
from litellm import Router