From 414866767176e3cb96bccee159bedc86ee1743db Mon Sep 17 00:00:00 2001 From: ryan-crabbe-berri Date: Sat, 23 May 2026 16:57:14 -0700 Subject: [PATCH] Fix spend logs v2 route permissions (#28705) Co-authored-by: Cursor Agent Co-authored-by: ryan-crabbe-berri --- litellm/proxy/_types.py | 3 ++ .../proxy/auth/test_route_checks.py | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/litellm/proxy/_types.py b/litellm/proxy/_types.py index 004f33e630..9046d52228 100644 --- a/litellm/proxy/_types.py +++ b/litellm/proxy/_types.py @@ -255,6 +255,7 @@ class KeyManagementRoutes(str, enum.Enum): # team spend-log viewing SPEND_LOGS = "/spend/logs" + SPEND_LOGS_V2 = "/spend/logs/v2" class LiteLLMRoutes(enum.Enum): @@ -548,6 +549,7 @@ class LiteLLMRoutes(enum.Enum): KeyManagementRoutes.TEAM_KEY_BULK_UPDATE.value, KeyManagementRoutes.TEAM_DAILY_ACTIVITY.value, KeyManagementRoutes.SPEND_LOGS.value, + KeyManagementRoutes.SPEND_LOGS_V2.value, KeyManagementRoutes.KEY_RESET_SPEND.value, KeyManagementRoutes.KEY_ALIASES.value, ] @@ -599,6 +601,7 @@ class LiteLLMRoutes(enum.Enum): "/spend/tags", "/spend/calculate", "/spend/logs", + "/spend/logs/v2", "/spend/logs/ui", "/spend/logs/session/ui", "/cost/estimate", diff --git a/tests/test_litellm/proxy/auth/test_route_checks.py b/tests/test_litellm/proxy/auth/test_route_checks.py index 4acf42996e..ad00c55a83 100644 --- a/tests/test_litellm/proxy/auth/test_route_checks.py +++ b/tests/test_litellm/proxy/auth/test_route_checks.py @@ -268,6 +268,47 @@ def test_mcp_management_routes_classified_as_management_not_llm_api(route): assert RouteChecks.is_management_route(route=route) is True +def test_spend_logs_v2_classified_as_management_not_llm_api(): + """Paginated spend logs are a management/spend read route, not an LLM API.""" + + assert RouteChecks.is_llm_api_route(route="/spend/logs/v2") is False + assert RouteChecks.is_management_route(route="/spend/logs/v2") is True + + +def test_virtual_key_management_routes_allows_spend_logs_v2(): + """Management virtual keys should be allowed to call the v2 spend logs endpoint.""" + + valid_token = UserAPIKeyAuth( + user_id="test_user", + allowed_routes=["management_routes"], + ) + + result = RouteChecks.is_virtual_key_allowed_to_call_route( + route="/spend/logs/v2", + valid_token=valid_token, + ) + + assert result is True + + +def test_virtual_key_llm_api_routes_denies_spend_logs_v2(): + """AI API virtual keys should not gain spend-log access.""" + + 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="/spend/logs/v2", + valid_token=valid_token, + ) + + assert exc_info.value.status_code == 403 + assert "Virtual key is not allowed to call this route" in str(exc_info.value.detail) + + @pytest.mark.parametrize( "route", [ @@ -1322,6 +1363,7 @@ ADMIN_VIEWER_LOGS_PAGE_ROUTES = [ "/cost/estimate", # Public spend logs / spend tracking routes that admin viewer should read "/spend/logs", + "/spend/logs/v2", "/spend/keys", "/spend/users", "/spend/tags",