fix(proxy/auth): prevent key hash and model allowlist disclosure on auth failures (#40217)

Signed-off-by: Arjun Pakhan <arjunpakhan@gmail.com>
This commit is contained in:
Arjun Pakhan 2026-09-08 10:12:19 +00:00
parent eeb7732fc1
commit 8252fc045d
5 changed files with 22 additions and 5 deletions

View file

@ -4031,7 +4031,7 @@ def _can_object_call_model(
return True
raise ProxyException(
message=f"{object_type} not allowed to access model. This {object_type} can only access models={models}. Tried to access {model}",
message=f"{object_type} not allowed to access model. Tried to access {model}",
type=ProxyErrorTypes.get_model_access_error_type_for_object(object_type=object_type),
param="model",
code=status.HTTP_403_FORBIDDEN,

View file

@ -1907,7 +1907,7 @@ async def _user_api_key_auth_builder(
)
except ProxyException as e:
if e.code == 401 or e.code == "401":
e.message = f"Authentication Error, Invalid proxy server token passed. Received API Key = {abbreviated_api_key}, Key Hash (Token) ={api_key}. Unable to find token in cache or `LiteLLM_VerificationTokenTable`"
e.message = f"Authentication Error, Invalid proxy server token passed. Received API Key = {abbreviated_api_key}, Unable to find token in cache or `LiteLLM_VerificationTokenTable`"
raise e
# update end-user params on valid token
# These can change per request - it's important to update them here

View file

@ -163,7 +163,7 @@ async def test_can_key_call_model(model, expect_to_work):
if expect_to_work:
await can_key_call_model(**args)
else:
with pytest.raises(Exception, match='key not allowed to access model\\. This key can only access') as e:
with pytest.raises(Exception, match='key not allowed to access model\\. Tried to access') as e:
await can_key_call_model(**args)
print(e)
@ -943,7 +943,7 @@ async def test_can_key_call_model_with_aliases(model, alias_map, expect_to_work)
llm_router=router,
)
else:
with pytest.raises(Exception, match='key not allowed to access model\\. This key can only access') as e:
with pytest.raises(Exception, match='key not allowed to access model\\. Tried to access') as e:
await can_key_call_model(
model=model,
llm_model_list=llm_model_list,

View file

@ -123,7 +123,7 @@ class TestCheckModelAccess:
"litellm.proxy.auth.auth_checks.can_key_call_model",
new_callable=AsyncMock,
side_effect=ProxyException(
message="key not allowed to access model. This key can only access models=['gpt-3.5-turbo']. Tried to access claude-3-opus-20240229",
message="key not allowed to access model. Tried to access claude-3-opus-20240229",
type="key_model_access_denied",
param="model",
code=401,

View file

@ -7462,3 +7462,20 @@ async def test_enforced_model_allowlists_reads_every_level_from_cache():
]
assert [list(scope) for scope in personal] == [[], [], [], ["o3"], []]
assert [list(scope) for scope in without_database] == [["gpt-4o"], ["gpt-4o-mini"]]
def test_model_access_denied_does_not_leak_allowlist():
"""Verify 403 error does not expose the full model allowlist (#40217)."""
import pytest
from litellm.proxy.auth.auth_checks import _can_object_call_model
from litellm.proxy.utils import ProxyException
with pytest.raises(ProxyException) as exc_info:
_can_object_call_model(
model="gpt-4o",
llm_router=None,
models=["gpt-3.5-turbo", "claude-3-5-sonnet"],
object_type="key"
)
assert str(exc_info.value.code) == "403"
assert "models=" not in exc_info.value.message
assert "Tried to access gpt-4o" in exc_info.value.message