Skip to content

refactor(proxy/auth): cherry-pick #29343 into patch/v1.87.0rc2 - #29365

Merged
yuneng-berri merged 1 commit into
patch/v1.87.0rc2from
patch/v1.87.0rc2-cp-29343
May 31, 2026
Merged

refactor(proxy/auth): cherry-pick #29343 into patch/v1.87.0rc2#29365
yuneng-berri merged 1 commit into
patch/v1.87.0rc2from
patch/v1.87.0rc2-cp-29343

Conversation

@yuneng-berri

Copy link
Copy Markdown
Contributor

Relevant issues

Cherry-pick of #29343 (merge commit 94a043efb2) onto the patch/v1.87.0rc2 branch (= tag v1.87.0-rc.2). Supersedes #29362, which targeted the older patch/v1.87.0rc1 base.

Linear ticket

n/a

Pre-Submission checklist

CI (LiteLLM team)

  • Branch creation CI run -- link:
  • CI run for the last commit -- link:
  • Merge / cherry-pick CI run -- links:

Bug verification on v1.87.0-rc.2

Confirmed the bug is still present at the v1.87.0-rc.2 tag (head of patch/v1.87.0rc2) before this cherry-pick lands. rc.2 = rc.1 + 2 patch commits, neither of which touched _types.py, so the rc.1 verification carries over unchanged.

$ git show v1.87.0-rc.2:litellm/proxy/_types.py | sed -n '/def _safe_hash_litellm_api_key/,/^    @/p'
    def _safe_hash_litellm_api_key(cls, api_key: str) -> str:
        """
        Helper to ensure all logged keys are hashed
        Covers:
        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)
        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

Any caller that passes the raw Authorization header value (Bearer sk-...) falls through to return api_key unchanged, so observability labels (e.g. Prometheus litellm_proxy_failed_requests_metric_total{hashed_api_key=...}) leak the literal Bearer sk-... string instead of the sha256 hash.

After this cherry-pick lands, the function body matches the source merge commit byte-for-byte — a case-insensitive Bearer strip runs before the existing sk-/JWT classification.

Screenshots / Proof of Fix

End-to-end proof-of-fix (curl-driven Prometheus scrape showing the metric row going from hashed_api_key="Bearer sk-..." to a proper sha256 hash) is captured in the original PR body at #29343 and was run against a live proxy with prometheus enabled. The cherry-pick is verbatim, so the same harness output applies.

Type

Refactoring + Test

Changes

Verbatim cherry-pick of #29343's merge commit 94a043efb226c5ccdbfc028fbb930ce45fb965eb onto patch/v1.87.0rc2. Auto-merged cleanly (no conflicts). 3 files, +30/-7 — matches the sum of the three original commits exactly (+8/-5 in _types.py, +4/-2 in the MCP auth test, +18/-0 for the new contract test). The _safe_hash_litellm_api_key body on this branch is byte-identical to the merge commit's version. Full rationale lives in the #29343 PR body.

…9343)

* refactor(proxy/auth): normalize Bearer prefix in safe-hash helper

UserAPIKeyAuth._safe_hash_litellm_api_key now strips a leading
"Bearer "/"bearer " prefix before its existing sk-/JWT classification, so
the helper produces the same hashed output regardless of whether the
caller stripped the Authorization header prefix or passed the header
value through unchanged.

* refactor(proxy/auth): make Bearer-prefix strip case-insensitive

Per RFC 7235 the HTTP authorization scheme token is case-insensitive.
Replace the two-prefix loop with a single case-insensitive check so the
helper normalizes "Bearer ", "bearer ", "BEARER ", and any mixed-case
variant before classifying the remainder as sk- or JWT. The contract
test gains coverage of "BEARER " and "BeArEr ".

* test(mcp): align auth-handler test expectations with safe-hash helper

The two MCP auth tests asserted that UserAPIKeyAuth(api_key="Bearer ...")
retained the raw header bytes on the api_key field. _safe_hash_litellm_api_key
now normalizes that input — stripping the Bearer prefix and hashing the
resulting sk- key — so the expectations move to the normalized form:
the bare token in the parametrize case, and hash_token("sk-...") in the
backward-compat assertion. This matches what the real auth flow produces
(the builder strips Bearer and the DB stores the hashed token), so the
mocks now line up with production rather than with the un-normalized
validator output.
@greptile-apps

greptile-apps Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This cherry-pick onto patch/v1.87.0rc2 fixes a key-leakage bug in UserAPIKeyAuth._safe_hash_litellm_api_key: when callers pass a raw Authorization header value (Bearer sk-...), the old code fell through to return api_key without hashing, leaking the literal Bearer sk-... string into Prometheus metric labels and other observability sinks. The fix adds a case-insensitive Bearer strip before the existing sk-/JWT classification logic.

  • litellm/proxy/_types.py: +3 lines strip Bearer prefix in _safe_hash_litellm_api_key before sk-/JWT dispatch; return normalized instead of return api_key so the prefix is also gone for opaque tokens.
  • tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py: two existing assertions updated — expected api_key no longer contains Bearer, and the sk- key assertion now checks the proper sha256 hash rather than the raw header string.
  • tests/test_litellm/proxy/test_proxy_types.py: new contract test asserting that four Bearer casing variants each produce the same api_key/token as a bare sk- key, and that neither starts with "bearer".

Confidence Score: 5/5

Safe to merge — the change is a small, well-scoped fix with no backwards-incompatible surface changes and is covered by a new contract test plus updated MCP auth tests.

The three-file cherry-pick touches only the _safe_hash_litellm_api_key normalisation path; the rest of the auth stack is untouched. The updated MCP assertions now check for correct hashed output rather than the leaked Bearer string, strengthening rather than weakening coverage. The new parametrized test in test_proxy_types.py locks in the four casing variants. No migration, schema, or router changes are involved.

No files require special attention.

Important Files Changed

Filename Overview
litellm/proxy/_types.py Adds case-insensitive Bearer prefix stripping in _safe_hash_litellm_api_key before sk-/JWT classification, fixing raw Bearer sk-... leaking into observability labels
tests/test_litellm/proxy/_experimental/mcp_server/auth/test_user_api_key_auth_mcp.py Two existing assertions updated to reflect correct post-fix behavior: expected API key now properly excludes Bearer prefix, and sk- key assertion now expects a hash instead of the raw Bearer string
tests/test_litellm/proxy/test_proxy_types.py New contract test verifying all four Bearer casing variants (Bearer, bearer, BEARER, BeArEr) produce the same hashed api_key/token as a bare sk- key

Reviews (1): Last reviewed commit: "refactor(proxy/auth): normalize Bearer p..." | Re-trigger Greptile

@yuneng-berri
yuneng-berri merged commit fa3fc7a into patch/v1.87.0rc2 May 31, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant