This commit is contained in:
Tanvir Alam 2026-09-30 10:29:46 -04:00 • committed by GitHub
commit c4d0ead825
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 1 deletions

View file

@ -284,7 +284,16 @@ class RouteChecks:
jwt_team_allowed_routes=RouteChecks._jwt_team_allowed_routes(valid_token=valid_token),
)
elif RouteChecks.is_llm_api_route(route=route):
pass
# View-only admins must not spend provider budget on inference.
# _check_proxy_admin_viewer_access already 403s LLM routes, but that
# branch sits below this early pass — so call it here first (#43478).
if _user_role == LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY.value:
RouteChecks._check_proxy_admin_viewer_access(
route=route,
_user_role=_user_role,
request_data=request_data,
request=request,
)
elif RouteChecks.is_info_route(route=route):
# check if user allowed to call an info route
if route == "/key/info":

View file

@ -206,6 +206,51 @@ def test_proxy_admin_viewer_config_update_route_rejected():
assert "role= proxy_admin_viewer" in str(exc_info.value.detail)
@pytest.mark.parametrize(
"inference_route",
[
"/v1/chat/completions",
"/chat/completions",
"/v1/embeddings",
"/v1/responses",
"/v1/images/generations",
],
)
def test_proxy_admin_viewer_inference_routes_rejected(inference_route):
"""proxy_admin_viewer must not call cost-incurring LLM routes (#43478).
`non_proxy_admin_allowed_routes_check` used to early-return on
`is_llm_api_route`, so `_check_proxy_admin_viewer_access` never ran for
/v1/chat/completions and friends — a view-only key could spend budget.
"""
user_obj = LiteLLM_UserTable(
user_id="viewer_user",
user_email="viewer@example.com",
user_role=LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY.value,
)
valid_token = UserAPIKeyAuth(
user_id="viewer_user",
user_role=LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY.value,
)
request = MagicMock(spec=Request)
request.query_params = {}
request.method = "POST"
with pytest.raises(HTTPException) as exc_info:
RouteChecks.non_proxy_admin_allowed_routes_check(
user_obj=user_obj,
_user_role=LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY.value,
route=inference_route,
request=request,
valid_token=valid_token,
request_data={},
)
assert exc_info.value.status_code == 403
assert "OpenAI routes" in str(exc_info.value.detail) or "not allowed" in str(exc_info.value.detail)
assert "proxy_admin_viewer" in str(exc_info.value.detail)
@pytest.mark.parametrize(
"blocked_route",
[