From 0c5fcde88386f144e218ab41661393de5282b211 Mon Sep 17 00:00:00 2001 From: mateo-berri <277851410+mateo-berri@users.noreply.github.com> Date: Tue, 18 Aug 2026 13:08:28 -0700 Subject: [PATCH] Revert "feat(key_management): let any authenticated user resolve a raw key via /key/info" This reverts commit d0c1d2be8a82723d458eaf133159ad8576bd9a8b. --- .../key_management_endpoints.py | 7 - .../test_key_management_endpoints.py | 146 ------------------ 2 files changed, 153 deletions(-) diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 99631377541..e4def45892b 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -6351,12 +6351,6 @@ async def _can_user_query_key_info( ) -> bool: """ Helper to check if the user has access to the key's info - - Any authenticated caller who presents the raw key value (i.e. a preimage of the - stored token hash) is allowed: possession of the raw key already grants the - ability to call /key/info with that key as the bearer token, so resolving - raw key -> key info discloses nothing new. Lookups by hashed token remain - restricted to admins, the key's owner, and the key's teammates. """ if ( ( @@ -6365,7 +6359,6 @@ async def _can_user_query_key_info( ) or user_api_key_dict.api_key == key or key_info.user_id == user_api_key_dict.user_id - or (key is not None and hash_token(token=key) == key_info.token) or await TeamMemberPermissionChecks.user_belongs_to_keys_team( user_api_key_dict=user_api_key_dict, existing_key_row=key_info, diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index fc349f8448d..cf9aa477112 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -15323,149 +15323,3 @@ async def test_rotate_master_key_rotates_sso_identity_assertions( prisma_client=mock_prisma_client, new_master_key="sk-new-master-key", ) - - -@pytest.mark.asyncio -async def test_can_user_query_key_info_raw_key_possession_allows_any_user(): - """ - Any authenticated user who presents the raw sk- key value can query that - key's info: possessing the raw key already lets them call /key/info with - the key itself as the bearer token, so this discloses nothing new. - """ - from litellm.proxy._types import hash_token - from litellm.proxy.management_endpoints.key_management_endpoints import ( - _can_user_query_key_info, - ) - - raw_key = "sk-raw-key-owned-by-someone-else" - key_info = LiteLLM_VerificationToken( - token=hash_token(raw_key), - user_id="key-owner", - team_id=None, - key_alias="prod-batch-alias", - ) - caller = UserAPIKeyAuth( - user_role=LitellmUserRoles.INTERNAL_USER, - user_id="unrelated-user", - api_key="hashed-caller-token", - ) - - assert ( - await _can_user_query_key_info( - user_api_key_dict=caller, - key=raw_key, - key_info=key_info, - ) - is True - ) - - -@pytest.mark.asyncio -async def test_can_user_query_key_info_hashed_token_still_forbidden(): - """ - Querying by hashed token (e.g. copied from spend logs) must stay - restricted to admins, the key's owner, and teammates. - """ - from litellm.proxy._types import hash_token - from litellm.proxy.management_endpoints.key_management_endpoints import ( - _can_user_query_key_info, - ) - - raw_key = "sk-raw-key-owned-by-someone-else" - key_info = LiteLLM_VerificationToken( - token=hash_token(raw_key), - user_id="key-owner", - team_id=None, - key_alias="prod-batch-alias", - ) - caller = UserAPIKeyAuth( - user_role=LitellmUserRoles.INTERNAL_USER, - user_id="unrelated-user", - api_key="hashed-caller-token", - ) - - assert ( - await _can_user_query_key_info( - user_api_key_dict=caller, - key=hash_token(raw_key), - key_info=key_info, - ) - is False - ) - - -@pytest.mark.asyncio -async def test_info_key_fn_resolves_alias_from_raw_key_for_any_user(monkeypatch): - """ - End-to-end through /key/info: a non-admin user unrelated to the key can - resolve raw sk- key -> key_alias. - """ - from litellm.proxy._types import hash_token - from litellm.proxy.management_endpoints.key_management_endpoints import info_key_fn - - raw_key = "sk-raw-key-owned-by-someone-else" - key_row = LiteLLM_VerificationToken( - token=hash_token(raw_key), - user_id="key-owner", - team_id=None, - key_alias="prod-batch-alias", - ) - - mock_prisma_client = AsyncMock() - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", AsyncMock()) - mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( - return_value=key_row - ) - - caller = UserAPIKeyAuth( - user_role=LitellmUserRoles.INTERNAL_USER, - user_id="unrelated-user", - api_key="hashed-caller-token", - ) - - result = await info_key_fn(key=raw_key, user_api_key_dict=caller) - - assert result["info"]["key_alias"] == "prod-batch-alias" - assert "token" not in result["info"] - - find_unique_kwargs = ( - mock_prisma_client.db.litellm_verificationtoken.find_unique.call_args.kwargs - ) - assert find_unique_kwargs["where"] == {"token": hash_token(raw_key)} - - -@pytest.mark.asyncio -async def test_info_key_fn_hashed_lookup_still_403_for_unrelated_user(monkeypatch): - """ - End-to-end through /key/info: the same unrelated user querying by hashed - token still gets a 403. - """ - from litellm.proxy._types import hash_token - from litellm.proxy.management_endpoints.key_management_endpoints import info_key_fn - - raw_key = "sk-raw-key-owned-by-someone-else" - key_row = LiteLLM_VerificationToken( - token=hash_token(raw_key), - user_id="key-owner", - team_id=None, - key_alias="prod-batch-alias", - ) - - mock_prisma_client = AsyncMock() - monkeypatch.setattr("litellm.proxy.proxy_server.prisma_client", mock_prisma_client) - monkeypatch.setattr("litellm.proxy.proxy_server.user_api_key_cache", AsyncMock()) - mock_prisma_client.db.litellm_verificationtoken.find_unique = AsyncMock( - return_value=key_row - ) - - caller = UserAPIKeyAuth( - user_role=LitellmUserRoles.INTERNAL_USER, - user_id="unrelated-user", - api_key="hashed-caller-token", - ) - - with pytest.raises(ProxyException) as exc_info: - await info_key_fn(key=hash_token(raw_key), user_api_key_dict=caller) - - assert int(exc_info.value.code) == 403