Add endpoint-level access control to /v2/user/info

Non-admin users can only query their own user_id. Previously relied
solely on the auth middleware route check, which proxy admins bypass.
This adds defense-in-depth directly in the handler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
yuneng-jiang 2026-03-04 21:27:41 -08:00
parent 47590b4fba
commit e6ac4cb230
2 changed files with 46 additions and 1 deletions

View file

@ -766,6 +766,19 @@ async def user_info_v2(
detail="user_id is required",
)
# Non-admin users can only query their own info
if (
user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN
and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY
and user_id != user_api_key_dict.user_id
):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Not allowed to access other user's info. Your user_id={}, requested user_id={}".format(
user_api_key_dict.user_id, user_id
),
)
user_info = await prisma_client.get_data(user_id=user_id)
if user_info is None:

View file

@ -1508,4 +1508,36 @@ async def test_user_info_v1_has_deprecation_header(mocker):
assert result.user_id == "test-user"
# Deprecation headers should be set on the response object
assert mock_response.headers.get("Deprecation") == "true"
assert "successor-version" in mock_response.headers.get("Link", "")
assert "successor-version" in mock_response.headers.get("Link", "")
@pytest.mark.asyncio
async def test_user_info_v2_non_admin_cannot_query_other_user(mocker):
"""
Test that a non-admin user gets 403 when querying another user's info
via /v2/user/info endpoint handler (defense-in-depth).
"""
from fastapi import Request
from litellm.proxy._types import ProxyException, UserAPIKeyAuth
from litellm.proxy.management_endpoints.internal_user_endpoints import (
user_info_v2,
)
mock_prisma_client = mocker.MagicMock()
mocker.patch("litellm.proxy.proxy_server.prisma_client", mock_prisma_client)
mock_request = mocker.MagicMock(spec=Request)
mock_user_api_key_dict = UserAPIKeyAuth(
user_id="user-A", user_role="internal_user"
)
with pytest.raises(ProxyException) as exc_info:
await user_info_v2(
user_id="user-B",
user_api_key_dict=mock_user_api_key_dict,
request=mock_request,
)
assert exc_info.value.code == "403"
assert "Not allowed" in str(exc_info.value.message)