mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-11 03:38:38 +00:00
fix(proxy): apply full key/team/user filter on by-id /v1/model/info
Addresses veria-ai Finding 2 on #29748. The 5638d58e bypass keyed off `user_api_key_dict.user_id is None` to short-circuit by-id authorization, but service-account virtual keys can carry `user_id=None` AND still have `models` / `team_models` restrictions — so a restricted service account could pull metadata for any deployment via `/v1/model/info?litellm_model_id=<disallowed-id>`. Drop the explicit master-key short-circuit and call `get_available_models_for_user` unconditionally — same call the listing path uses. `_apply_user_models_filter` already bypasses the user.models step when `user_id is None` (master key / service account), but `get_key_models` + `get_team_models` still narrow to the key/team allowlist for service accounts. By-id authorization now tracks the listing path exactly. Test fixes: - `test_model_info_endpoint_returns_defaults_for_specific_model_id`: add the missing patches for `get_key_models` / `get_team_models` / `get_complete_model_list` + stub `mock_router.get_model_names` / `.get_model_access_groups` so by-id authorization sees a valid allowlist. 18/18 pass locally. 403 message broadened to "not authorized for this key/user" since the rejection can fire on key/team restrictions, not just user.models.
This commit is contained in:
parent
3cdde571d1
commit
968835b5e4
2 changed files with 42 additions and 25 deletions
|
|
@ -13445,33 +13445,37 @@ async def model_info_v1(
|
|||
"error": f"Model id = {litellm_model_id} not found on litellm proxy"
|
||||
},
|
||||
)
|
||||
# Authorize against user.models — by-id was previously
|
||||
# short-circuiting before the listing-path filter. Master
|
||||
# key / service accounts (no user_id) bypass, matching
|
||||
# `_apply_user_models_filter` semantics.
|
||||
if user_api_key_dict.user_id is not None:
|
||||
from litellm.proxy.utils import get_available_models_for_user
|
||||
# Authorize against key/team/user models — by-id was previously
|
||||
# short-circuiting before the listing-path filter, letting a
|
||||
# restricted virtual key (including service accounts with no
|
||||
# user_id but with key.models or team_models constraints) pull
|
||||
# metadata for any deployment via /v1/model/info?litellm_model_id=.
|
||||
# Reuse `get_available_models_for_user` so by-id authorization
|
||||
# tracks the listing-path filter exactly: key.models, team_models,
|
||||
# and user.models all narrow the allowlist; `_apply_user_models_filter`
|
||||
# bypasses the user.models step when user_id is absent.
|
||||
from litellm.proxy.utils import get_available_models_for_user
|
||||
|
||||
authorized_models = await get_available_models_for_user(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
llm_router=llm_router,
|
||||
general_settings=general_settings,
|
||||
user_model=user_model,
|
||||
prisma_client=prisma_client,
|
||||
proxy_logging_obj=proxy_logging_obj,
|
||||
team_id=None,
|
||||
include_model_access_groups=False,
|
||||
only_model_access_groups=False,
|
||||
return_wildcard_routes=False,
|
||||
user_api_key_cache=user_api_key_cache,
|
||||
authorized_models = await get_available_models_for_user(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
llm_router=llm_router,
|
||||
general_settings=general_settings,
|
||||
user_model=user_model,
|
||||
prisma_client=prisma_client,
|
||||
proxy_logging_obj=proxy_logging_obj,
|
||||
team_id=None,
|
||||
include_model_access_groups=False,
|
||||
only_model_access_groups=False,
|
||||
return_wildcard_routes=False,
|
||||
user_api_key_cache=user_api_key_cache,
|
||||
)
|
||||
if deployment_info.model_name not in authorized_models:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
"error": f"Model id = {litellm_model_id} not authorized for this key/user"
|
||||
},
|
||||
)
|
||||
if deployment_info.model_name not in authorized_models:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail={
|
||||
"error": f"Model id = {litellm_model_id} not authorized for this user"
|
||||
},
|
||||
)
|
||||
_deployment_info_dict = _get_proxy_model_info(
|
||||
model=deployment_info.model_dump(exclude_none=True)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -115,6 +115,11 @@ class TestModelInfoEndpointWithRouter:
|
|||
|
||||
mock_router = MagicMock()
|
||||
mock_router.get_deployment.return_value = deployment
|
||||
# By-id now authorizes against the listing-path filter; stub
|
||||
# what `get_available_models_for_user` reads from the router so
|
||||
# "model1" is in the allowlist.
|
||||
mock_router.get_model_names.return_value = ["model1"]
|
||||
mock_router.get_model_access_groups.return_value = {}
|
||||
|
||||
user_api_key_dict = UserAPIKeyAuth(api_key="sk-test")
|
||||
|
||||
|
|
@ -122,6 +127,14 @@ class TestModelInfoEndpointWithRouter:
|
|||
patch("litellm.proxy.proxy_server.llm_router", mock_router),
|
||||
patch("litellm.proxy.proxy_server.llm_model_list", []),
|
||||
patch("litellm.proxy.proxy_server.user_model", None),
|
||||
patch("litellm.proxy.proxy_server.get_key_models", return_value=["model1"]),
|
||||
patch(
|
||||
"litellm.proxy.proxy_server.get_team_models", return_value=["model1"]
|
||||
),
|
||||
patch(
|
||||
"litellm.proxy.proxy_server.get_complete_model_list",
|
||||
return_value=["model1"],
|
||||
),
|
||||
):
|
||||
response = await model_info_v1(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue