Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 8 additions & 5 deletions litellm/proxy/_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -2741,13 +2741,16 @@ def _safe_hash_litellm_api_key(cls, api_key: str) -> str:
1. Regular API keys from LiteLLM DB
2. JWT tokens used for connecting to LiteLLM API
"""
if api_key.startswith("sk-"):
return hash_token(api_key)
normalized = api_key
if normalized[:7].lower() == "bearer ":
normalized = normalized[7:]
if normalized.startswith("sk-"):
return hash_token(normalized)
from litellm.proxy.auth.handle_jwt import JWTHandler

if JWTHandler.is_jwt(token=api_key):
return f"hashed-jwt-{hash_token(token=api_key)}"
return api_key
if JWTHandler.is_jwt(token=normalized):
return f"hashed-jwt-{hash_token(token=normalized)}"
return normalized

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The fallback return normalized returns the prefix-stripped token for keys that are neither sk-* nor JWT. For an opaque Bearer <token> value that matches neither case, the old code returned Bearer <token> verbatim (unhashed), while the new code returns <token> (also unhashed). Both are technically correct — the pre-existing behaviour was already not hashing this class of input — but a clarifying comment here would make the intentional non-hashing explicit for future readers.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!


@classmethod
def get_litellm_internal_health_check_user_api_key_auth(cls) -> "UserAPIKeyAuth":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ async def test_permission_inheritance_edge_cases(self):
# Test case 2: Authorization header present (fallback)
(
[(b"authorization", b"Bearer test-auth-token")],
"Bearer test-auth-token",
"test-auth-token",
None,
{},
),
Expand Down Expand Up @@ -674,7 +674,9 @@ async def mock_user_api_key_auth(api_key, request):
) = await MCPRequestHandler.process_mcp_request(scope)

# Should succeed with the LiteLLM key from Authorization header
assert auth_result.api_key == "Bearer sk-litellm-valid-key"
from litellm.proxy.utils import hash_token

assert auth_result.api_key == hash_token("sk-litellm-valid-key")
Comment on lines 676 to +679

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The hash_token import inside the assertion block pulls from litellm.proxy.utils, while _safe_hash_litellm_api_key (in _types.py) calls its own local hash_token defined at line 218. Both are identical SHA-256 implementations today, so the assertion is valid — but if either copy is changed independently the test would silently diverge. Importing from _types keeps the test coupled to the same symbol the production path uses.

Suggested change
# Should succeed with the LiteLLM key from Authorization header
assert auth_result.api_key == "Bearer sk-litellm-valid-key"
from litellm.proxy.utils import hash_token
assert auth_result.api_key == hash_token("sk-litellm-valid-key")
# Should succeed with the LiteLLM key from Authorization header
from litellm.proxy._types import hash_token
assert auth_result.api_key == hash_token("sk-litellm-valid-key")

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

mock_auth.assert_called_once()

async def test_non_auth_http_exception_still_raises(self):
Expand Down
18 changes: 18 additions & 0 deletions tests/test_litellm/proxy/test_proxy_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,21 @@ def test_internal_jobs_user_has_proxy_admin_role():
assert system_user.user_id == "system"
assert system_user.team_id == "system"
assert system_user.team_alias == "system"


def test_user_api_key_auth_hashes_authorization_header_form_of_key():
from litellm.proxy._types import UserAPIKeyAuth

raw_key = "sk-AbCdEfGhIjKlMnOpQrStUvWxYz0123456789"
baseline = UserAPIKeyAuth(api_key=raw_key)

for header_form in (
f"Bearer {raw_key}",
f"bearer {raw_key}",
f"BEARER {raw_key}",
f"BeArEr {raw_key}",
):
from_header = UserAPIKeyAuth(api_key=header_form)
assert from_header.api_key == baseline.api_key
assert from_header.token == baseline.token
assert not from_header.api_key.lower().startswith("bearer")
Loading