diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 23fe7af9994..e8355f4941a 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -517,6 +517,11 @@ class LiteLLMRoutes(enum.Enum): "/guardrails/apply_guardrail", ] + model_info_routes = [ + "/model/info", + "/v1/model/info", + ] + llm_api_routes = ( openai_routes + anthropic_routes @@ -527,6 +532,7 @@ class LiteLLMRoutes(enum.Enum): + mcp_inference_routes + litellm_native_routes + agent_routes + + model_info_routes ) info_routes = [ "/key/info", @@ -653,8 +659,8 @@ class LiteLLMRoutes(enum.Enum): "/global/spend/all_tag_names", ] - public_routes = set( - [ + public_routes = frozenset( + ( "/routes", "/", "/health/liveliness", @@ -669,7 +675,7 @@ class LiteLLMRoutes(enum.Enum): "/public/mcp_hub", "/public/skill_hub", "/public/litellm_model_cost_map", - ] + ) ) # Retained for backwards compatibility with JWT auth configs that reference diff --git a/litellm/proxy/management_endpoints/team_endpoints.py b/litellm/proxy/management_endpoints/team_endpoints.py index 1d4cb277c2e..1efd0a747df 100644 --- a/litellm/proxy/management_endpoints/team_endpoints.py +++ b/litellm/proxy/management_endpoints/team_endpoints.py @@ -22,7 +22,6 @@ from typing import ( Mapping, Optional, Protocol, - Sequence, Tuple, TypeVar, Union, diff --git a/ruff-strict-budget.json b/ruff-strict-budget.json index b8650eea7aa..57f69dfffe7 100644 --- a/ruff-strict-budget.json +++ b/ruff-strict-budget.json @@ -69,7 +69,7 @@ "limit": 4 }, "C405": { - "limit": 23 + "limit": 22 }, "C408": { "limit": 14 diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index 06764139eda..1426876783b 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -445,6 +445,60 @@ def test_virtual_key_llm_api_routes_allows_mcp_inference_endpoints(route, method assert result is True +@pytest.mark.parametrize("route", ["/model/info", "/v1/model/info"]) +def test_virtual_key_llm_api_routes_allows_model_info(route): + """AI API virtual keys must be able to read model metadata (pricing, mode, + max_tokens) for the deployments they can already route to. Both the + unversioned and /v1 paths are the same handler, so both must be reachable. + """ + + 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"]) +def test_model_info_not_classified_as_llm_api(route): + """Membership in `llm_api_routes` must not promote /model/info 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 + outside all of them. + """ + + assert RouteChecks.is_llm_api_route(route=route) is False + + +@pytest.mark.parametrize("route", ["/v2/model/info", "/model_group/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. + """ + + valid_token = UserAPIKeyAuth( + user_id="test_user", + allowed_routes=["llm_api_routes"], + ) + + with pytest.raises(HTTPException) as exc_info: + RouteChecks.is_virtual_key_allowed_to_call_route( + route=route, + valid_token=valid_token, + request=_mock_request("GET"), + ) + + assert exc_info.value.status_code == 403 + + def test_spend_logs_v2_classified_as_management_not_llm_api(): """Paginated spend logs are a management/spend read route, not an LLM API.""" diff --git a/type-discipline-budget.json b/type-discipline-budget.json index ff037a2872e..b790d9acb0f 100644 --- a/type-discipline-budget.json +++ b/type-discipline-budget.json @@ -3,7 +3,7 @@ "limit": 23191 }, "LIT002": { - "limit": 27276 + "limit": 27275 }, "LIT003": { "limit": 292