diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 273ec695f6..10ffb7403f 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -618,6 +618,11 @@ class LiteLLMRoutes(enum.Enum): "/config/yaml", "/litellm/.well-known/litellm-ui-config", "/.well-known/litellm-ui-config", + "/public/model_hub", + "/public/model_hub/info", + "/public/agent_hub", + "/public/mcp_hub", + "/public/skill_hub", "/public/litellm_model_cost_map", ] ) diff --git a/litellm/proxy/auth/user_api_key_auth.py b/litellm/proxy/auth/user_api_key_auth.py index da9be3bc46..4fb82e2254 100644 --- a/litellm/proxy/auth/user_api_key_auth.py +++ b/litellm/proxy/auth/user_api_key_auth.py @@ -86,16 +86,6 @@ except ImportError as e: user_api_key_service_logger_obj = ServiceLogging() # used for tracking latency on OTEL -_PUBLIC_AI_HUB_ROUTES = frozenset( - { - "/public/model_hub", - "/public/model_hub/info", - "/public/agent_hub", - "/public/mcp_hub", - "/public/skill_hub", - } -) - def _normalize_public_auth_route(route: str) -> str: if route != "/" and route.endswith("/"): @@ -110,11 +100,6 @@ def _route_requires_auth_despite_public( if normalized_route == "/metrics": return litellm.require_auth_for_metrics_endpoint is not False - if normalized_route in _PUBLIC_AI_HUB_ROUTES: - return (general_settings or {}).get( - "require_auth_for_public_ai_hub", True - ) is True - return False diff --git a/litellm/proxy/public_endpoints/public_endpoints.py b/litellm/proxy/public_endpoints/public_endpoints.py index 827a59b2e0..7d9da543c7 100644 --- a/litellm/proxy/public_endpoints/public_endpoints.py +++ b/litellm/proxy/public_endpoints/public_endpoints.py @@ -5,7 +5,7 @@ from importlib.resources import files from typing import Any, Dict, List, Optional import litellm -from fastapi import APIRouter, Depends, HTTPException, Request +from fastapi import APIRouter, HTTPException from litellm._logging import verbose_logger from litellm.litellm_core_utils.get_blog_posts import ( @@ -16,11 +16,7 @@ from litellm.litellm_core_utils.get_blog_posts import ( ) from litellm.proxy._types import ( CommonProxyErrors, - LitellmUserRoles, - SpecialHeaders, - UserAPIKeyAuth, ) -from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.types.agents import AgentCard from litellm.types.mcp import MCPPublicServer from litellm.types.proxy.management_endpoints.model_management_endpoints import ( @@ -37,36 +33,6 @@ from litellm.types.utils import LlmProviders router = APIRouter() -async def public_ai_hub_auth_dependency(request: Request) -> UserAPIKeyAuth: - from litellm.proxy.proxy_server import general_settings - - if (general_settings or {}).get("require_auth_for_public_ai_hub", True) is True: - return await user_api_key_auth( - request=request, - api_key=request.headers.get(SpecialHeaders.openai_authorization.value) - or "", - azure_api_key_header=request.headers.get( - SpecialHeaders.azure_authorization.value - ) - or "", - anthropic_api_key_header=request.headers.get( - SpecialHeaders.anthropic_authorization.value - ), - google_ai_studio_api_key_header=request.headers.get( - SpecialHeaders.google_ai_studio_authorization.value - ), - azure_apim_header=request.headers.get( - SpecialHeaders.azure_apim_authorization.value - ) - or "", - custom_litellm_key_header=request.headers.get( - SpecialHeaders.custom_litellm_api_key.value - ), - ) - - return UserAPIKeyAuth(user_role=LitellmUserRoles.INTERNAL_USER_VIEW_ONLY) - - # --------------------------------------------------------------------------- # /public/endpoints — helpers # --------------------------------------------------------------------------- @@ -189,7 +155,6 @@ def _load_endpoints() -> List[Dict[str, Any]]: @router.get( "/public/model_hub", tags=["public", "model management"], - dependencies=[Depends(public_ai_hub_auth_dependency)], response_model=List[ModelGroupInfoProxy], ) async def public_model_hub(): @@ -244,7 +209,6 @@ async def public_model_hub(): @router.get( "/public/agent_hub", tags=["[beta] Agents", "public"], - dependencies=[Depends(public_ai_hub_auth_dependency)], response_model=List[AgentCard], ) async def get_agents(): @@ -266,7 +230,6 @@ async def get_agents(): @router.get( "/public/mcp_hub", tags=["[beta] MCP", "public"], - dependencies=[Depends(public_ai_hub_auth_dependency)], response_model=List[MCPPublicServer], ) async def get_mcp_servers(): @@ -286,7 +249,6 @@ async def get_mcp_servers(): @router.get( "/public/skill_hub", tags=["public", "Claude Code Marketplace"], - dependencies=[Depends(public_ai_hub_auth_dependency)], ) async def public_skill_hub(): """Return enabled (public) Claude Code skills — no auth required.""" @@ -333,7 +295,6 @@ async def public_skill_hub(): @router.get( "/public/model_hub/info", tags=["public", "model management"], - dependencies=[Depends(public_ai_hub_auth_dependency)], response_model=PublicModelHubInfo, ) async def public_model_hub_info(): diff --git a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py index a5cf52b071..a7efe97157 100644 --- a/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py +++ b/litellm/proxy/ui_crud_endpoints/proxy_setting_endpoints.py @@ -94,11 +94,6 @@ class UISettings(BaseModel): description="List of page keys that internal users (non-admins) can see in the UI sidebar. If not set, all pages are visible based on role permissions.", ) - require_auth_for_public_ai_hub: bool = Field( - default=True, - description="If true, requires authentication for accessing the public AI Hub.", - ) - allow_public_health_readiness_details: bool = Field( default=False, description="If true, returns the legacy detailed payload from the unauthenticated /health/readiness endpoint.", @@ -173,7 +168,6 @@ ALLOWED_UI_SETTINGS_FIELDS = { "disable_model_add_for_internal_users", "disable_team_admin_delete_team_user", "enabled_ui_pages_internal_users", - "require_auth_for_public_ai_hub", "allow_public_health_readiness_details", "forward_client_headers_to_llm_api", "forward_llm_provider_auth_headers", @@ -189,7 +183,6 @@ ALLOWED_UI_SETTINGS_FIELDS = { # Flags that must be synced from the persisted UISettings into # general_settings at runtime (on both read and write). _RUNTIME_GENERAL_SETTINGS_FLAGS = [ - "require_auth_for_public_ai_hub", "allow_public_health_readiness_details", "forward_client_headers_to_llm_api", "forward_llm_provider_auth_headers", diff --git a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py index 0cce501a15..3f28191bce 100644 --- a/tests/test_litellm/proxy/auth/test_user_api_key_auth.py +++ b/tests/test_litellm/proxy/auth/test_user_api_key_auth.py @@ -15,6 +15,7 @@ import litellm import litellm.proxy.proxy_server from litellm.caching.dual_cache import DualCache from litellm.proxy._types import ( + LiteLLMRoutes, LiteLLM_JWTAuth, LiteLLM_UserTable, LitellmUserRoles, @@ -62,25 +63,16 @@ def test_route_requires_auth_despite_public_for_metrics(monkeypatch): assert _route_requires_auth_despite_public("/metrics", {}) is False -def test_route_requires_auth_despite_public_for_public_ai_hub(): - settings = {"require_auth_for_public_ai_hub": True} - - assert _route_requires_auth_despite_public("/public/model_hub", {}) is True - assert _route_requires_auth_despite_public("/public/model_hub", settings) is True - assert _route_requires_auth_despite_public("/public/model_hub/", settings) is True - assert ( - _route_requires_auth_despite_public("/public/model_hub/info", settings) is True - ) - assert _route_requires_auth_despite_public("/public/agent_hub", settings) is True - assert _route_requires_auth_despite_public("/public/mcp_hub", settings) is True - assert _route_requires_auth_despite_public("/public/skill_hub", settings) is True - - assert ( - _route_requires_auth_despite_public( - "/public/model_hub", {"require_auth_for_public_ai_hub": False} - ) - is False - ) +def test_public_ai_hub_routes_remain_public(): + for route in ( + "/public/model_hub", + "/public/model_hub/info", + "/public/agent_hub", + "/public/mcp_hub", + "/public/skill_hub", + ): + assert route in LiteLLMRoutes.public_routes.value + assert _route_requires_auth_despite_public(route, {}) is False @pytest.mark.asyncio diff --git a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py index f39e39c565..f82da59899 100644 --- a/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py +++ b/tests/test_litellm/proxy/public_endpoints/test_public_endpoints.py @@ -9,15 +9,9 @@ sys.path.insert(0, os.path.abspath("../../..")) from fastapi import FastAPI from fastapi.testclient import TestClient -from starlette.datastructures import URL -from starlette.requests import Request -from litellm.proxy._types import LitellmUserRoles, ProxyException from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.public_endpoints import router -from litellm.proxy.public_endpoints.public_endpoints import ( - public_ai_hub_auth_dependency, -) from litellm.types.proxy.management_endpoints.model_management_endpoints import ( ModelGroupInfoProxy, ) @@ -97,7 +91,7 @@ def test_get_litellm_model_cost_map_returns_cost_map(): ) -def test_public_ai_hub_info_requires_auth_by_default(monkeypatch): +def test_public_ai_hub_info_is_public_by_default(monkeypatch): app = FastAPI() app.include_router(router) client = TestClient(app) @@ -105,68 +99,11 @@ def test_public_ai_hub_info_requires_auth_by_default(monkeypatch): monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {}) monkeypatch.setattr("litellm.proxy.proxy_server.master_key", "sk-master") - with pytest.raises(ProxyException): - client.get("/public/model_hub/info") - - -def test_public_ai_hub_info_can_be_explicitly_public(monkeypatch): - app = FastAPI() - app.include_router(router) - client = TestClient(app) - - monkeypatch.setattr( - "litellm.proxy.proxy_server.general_settings", - {"require_auth_for_public_ai_hub": False}, - ) - monkeypatch.setattr("litellm.proxy.proxy_server.master_key", "sk-master") - response = client.get("/public/model_hub/info") assert response.status_code == 200, response.text -@pytest.mark.asyncio -async def test_public_ai_hub_info_requires_auth_by_default_dependency(monkeypatch): - monkeypatch.setattr( - "litellm.proxy.proxy_server.general_settings", - {}, - ) - monkeypatch.setattr("litellm.proxy.proxy_server.master_key", "sk-master") - request = Request( - scope={ - "type": "http", - "method": "GET", - "path": "/public/model_hub/info", - "headers": [], - } - ) - request._url = URL(url="/public/model_hub/info") - - with pytest.raises(ProxyException): - await public_ai_hub_auth_dependency(request) - - -@pytest.mark.asyncio -async def test_public_ai_hub_info_skips_auth_when_explicitly_disabled(monkeypatch): - monkeypatch.setattr( - "litellm.proxy.proxy_server.general_settings", - {"require_auth_for_public_ai_hub": False}, - ) - request = Request( - scope={ - "type": "http", - "method": "GET", - "path": "/public/model_hub/info", - "headers": [], - } - ) - request._url = URL(url="/public/model_hub/info") - - auth = await public_ai_hub_auth_dependency(request) - - assert auth.user_role == LitellmUserRoles.INTERNAL_USER_VIEW_ONLY - - def test_watsonx_provider_fields(): """Test that Watsonx provider has all required credential fields including multiple auth options.""" app = FastAPI() diff --git a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py index 256782c714..8e67c7e87e 100644 --- a/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py +++ b/tests/test_litellm/proxy/ui_crud_endpoints/test_proxy_setting_endpoints.py @@ -1147,43 +1147,6 @@ class TestProxySettingEndpoints: assert response.status_code == 200 assert general_settings.get("forward_llm_provider_auth_headers") is True - def test_update_ui_settings_syncs_public_ai_hub_auth_to_general_settings( - self, mock_auth, monkeypatch - ): - """Public AI Hub auth flag must be synced so public-route auth checks see it.""" - from unittest.mock import AsyncMock, MagicMock - - from litellm.proxy._types import UserAPIKeyAuth - from litellm.proxy.auth.user_api_key_auth import user_api_key_auth - - mock_user_auth = UserAPIKeyAuth( - user_id="test-user-123", - user_role=LitellmUserRoles.PROXY_ADMIN, - ) - app.dependency_overrides[user_api_key_auth] = lambda: mock_user_auth - - monkeypatch.setattr("litellm.proxy.proxy_server.store_model_in_db", True) - - general_settings: dict = {} - monkeypatch.setattr( - "litellm.proxy.proxy_server.general_settings", general_settings - ) - - mock_prisma = MagicMock() - mock_prisma.db.litellm_uisettings.upsert = AsyncMock() - mock_prisma.db.litellm_uisettings.find_unique = AsyncMock(return_value=None) - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma) - - payload = {"require_auth_for_public_ai_hub": True} - - try: - response = client.patch("/update/ui_settings", json=payload) - finally: - app.dependency_overrides.clear() - - assert response.status_code == 200 - assert general_settings.get("require_auth_for_public_ai_hub") is True - def test_update_ui_settings_syncs_public_health_readiness_details_to_general_settings( self, mock_auth, monkeypatch ):