mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-11 22:51:28 +00:00
fix(proxy): let llm_api keys read /model_group/info
Keys created with key_type=llm_api get allowed_routes=["llm_api_routes"],
which covered /v1/models and /model/info but not /model_group/info, so an
OpenAI-compatible client could list models yet got a 403 when reading the
group-level pricing, mode and context-window metadata it needs at request
time (BerriAI/litellm#37810).
Adds model_group_info_routes as a new LiteLLMRoutes member included in
llm_api_routes only. As with /model/info, membership there is not the same
as RouteChecks.is_llm_api_route(), so budget reservation, cost tracking and
DISABLE_LLM_API_ENDPOINTS are unaffected; common_checks already treats the
path as spend-free via MODEL_DISCOVERY_ROUTES. The handler scopes its
response to the caller's model access and returns no api_base or
credentials. /v2/model/info stays out: it is the paginated Admin UI
deployment listing, per the scope set in 7d6ee2a9c
This commit is contained in:
parent
b9bff0998c
commit
a2ef0ce560
2 changed files with 32 additions and 5 deletions
|
|
@ -567,6 +567,10 @@ class LiteLLMRoutes(enum.Enum):
|
|||
"/v1/model/info",
|
||||
]
|
||||
|
||||
model_group_info_routes = [
|
||||
"/model_group/info",
|
||||
]
|
||||
|
||||
llm_api_routes = (
|
||||
openai_routes
|
||||
+ anthropic_routes
|
||||
|
|
@ -578,6 +582,7 @@ class LiteLLMRoutes(enum.Enum):
|
|||
+ litellm_native_routes
|
||||
+ list(agent_inference_routes)
|
||||
+ model_info_routes
|
||||
+ model_group_info_routes
|
||||
)
|
||||
info_routes = [
|
||||
"/key/info",
|
||||
|
|
|
|||
|
|
@ -527,9 +527,31 @@ def test_virtual_key_llm_api_routes_allows_model_info(route):
|
|||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route", ["/model/info", "/v1/model/info"])
|
||||
@pytest.mark.parametrize("route", ["/model_group/info"])
|
||||
def test_virtual_key_llm_api_routes_allows_model_group_info(route):
|
||||
"""AI API virtual keys must be able to read group-level model metadata
|
||||
(pricing, mode, context window) for the groups they can already call.
|
||||
The handler scopes its response to the caller's model access and returns
|
||||
no api_base or credentials.
|
||||
"""
|
||||
|
||||
valid_token = UserAPIKeyAuth(
|
||||
user_id="test_user",
|
||||
allowed_routes=["llm_api_routes"],
|
||||
)
|
||||
|
||||
result = RouteChecks.is_virtual_key_allowed_to_call_route(
|
||||
route=route,
|
||||
valid_token=valid_token,
|
||||
request=_mock_request("GET"),
|
||||
)
|
||||
|
||||
assert result is True
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route", ["/model/info", "/v1/model/info", "/model_group/info"])
|
||||
def test_model_info_not_classified_as_llm_api(route):
|
||||
"""Membership in `llm_api_routes` must not promote /model/info to an
|
||||
"""Membership in `llm_api_routes` must not promote model-metadata reads to an
|
||||
`is_llm_api_route()`. That predicate gates DISABLE_LLM_API_ENDPOINTS,
|
||||
global/virtual-key budget enforcement, enforce_user_param and the JWT
|
||||
x-litellm-team-id attachment; model metadata is a free read and must stay
|
||||
|
|
@ -539,10 +561,10 @@ def test_model_info_not_classified_as_llm_api(route):
|
|||
assert RouteChecks.is_llm_api_route(route=route) is False
|
||||
|
||||
|
||||
@pytest.mark.parametrize("route", ["/v2/model/info", "/model_group/info"])
|
||||
@pytest.mark.parametrize("route", ["/v2/model/info"])
|
||||
def test_virtual_key_llm_api_routes_denies_other_model_info_routes(route):
|
||||
"""The grant is scoped to the two /model/info paths. The paginated Admin UI
|
||||
listing and the model-group endpoint stay outside it.
|
||||
"""The grant covers /model_group/info (group-level metadata scoped to the
|
||||
caller's model access) but not the paginated Admin UI deployment listing.
|
||||
"""
|
||||
|
||||
valid_token = UserAPIKeyAuth(
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue