[Fix] Key Update Endpoint Returns 401 Instead of 404 for Nonexistent Keys#24063
Conversation
The /key/update endpoint's get_data() call raises a 401 when the body `key` field doesn't exist in the DB, because get_data() treats the token as an auth credential. This caused the auth layer to resolve the body key instead of the Authorization header bearer token. Replace prisma_client.get_data() with direct Prisma find_unique() in both _get_and_validate_existing_key() and update_key_fn(), matching the pattern used in the /key/block and /key/unblock fix (PR #23977). Also fix the incorrect "Team not found" error message in update_key_fn. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Greptile SummaryThis PR fixes a bug where Root cause: Key changes:
Confidence Score: 5/5
|
| Filename | Overview |
|---|---|
| litellm/proxy/management_endpoints/key_management_endpoints.py | Root-cause fix: replaces get_data() with find_unique() + _hash_token_if_needed() in _get_and_validate_existing_key(), and consolidates the duplicate key-lookup in update_key_fn() to delegate to that helper. Also fixes the stale "Team not found" error message. Exception propagation through the existing try/except is correct (ProxyException is re-raised as-is). |
| tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py | Adds test_update_key_nonexistent_key_returns_404: a pure-mock unit test (no real network calls) consistent with the project's test pattern. Correctly asserts code == "404", message contains "not found", and "Authentication Error" is absent. The assertion matches ProxyException's self.code = str(code) semantics. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Client: POST /key/update] --> B{DB connected?}
B -- No --> C[HTTPException 500]
B -- Yes --> D[Hash token via SHA-256]
D --> E[Query DB via find_unique]
E --> F{Record found?}
F -- No --> G[Raise ProxyException 404\nNot Found]
F -- Yes --> H[Validate update data]
H --> I[Prepare update fields]
I --> J[Update DB record]
J --> L[Invalidate cache entry]
L --> M[Return success response]
G --> N{Caught in except block}
N -- ProxyException --> O[Re-raised as-is\nHTTP 404 to client]
N -- HTTPException --> P[Wrapped as auth_error\nProxyException]
Last reviewed commit: "fix: address review ..."
| from litellm.proxy.proxy_server import hash_token | ||
|
|
||
| if token.startswith("sk-"): | ||
| hashed_token = hash_token(token=token) | ||
| else: | ||
| hashed_token = token | ||
|
|
||
| existing_key_row = await prisma_client.db.litellm_verificationtoken.find_unique( | ||
| where={"token": hashed_token} | ||
| ) | ||
|
|
||
| if existing_key_row is None: | ||
| raise HTTPException( | ||
| status_code=404, | ||
| detail={"error": f"Key not found: {token}"}, | ||
| raise ProxyException( | ||
| message=f"Key not found. Passed key={token}", | ||
| type=ProxyErrorTypes.not_found_error, | ||
| param="key", | ||
| code=status.HTTP_404_NOT_FOUND, | ||
| ) | ||
|
|
||
| return existing_key_row |
There was a problem hiding this comment.
No automated test for the fixed code path
PR #23977 (the block/unblock fix this PR is modelled on) added test_block_key_nonexistent_key_returns_404 and test_unblock_key_nonexistent_key_returns_404 to prove the fix works. This PR has no equivalent test for /key/update.
Without a mock-based unit test that verifies update_key_fn() raises a ProxyException with HTTP 404 (and not 401) when find_unique returns None, there is no automated regression guard for the reported bug. If the project's custom rule requires evidence of resolution, this should be addressed before merge.
A test similar to the block/unblock ones already present in tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py would be sufficient.
| from litellm.proxy.proxy_server import hash_token | ||
|
|
||
| if data.key.startswith("sk-"): | ||
| hashed_token = hash_token(token=data.key) | ||
| else: | ||
| hashed_token = data.key | ||
|
|
||
| existing_key_row = await prisma_client.db.litellm_verificationtoken.find_unique( | ||
| where={"token": hashed_token} | ||
| ) | ||
|
|
||
| if existing_key_row is None: | ||
| raise HTTPException( | ||
| status_code=404, | ||
| detail={"error": f"Team not found, passed team_id={data.team_id}"}, | ||
| raise ProxyException( | ||
| message=f"Key not found. Passed key={data.key}", | ||
| type=ProxyErrorTypes.not_found_error, | ||
| param="key", | ||
| code=status.HTTP_404_NOT_FOUND, | ||
| ) |
There was a problem hiding this comment.
Duplicated key-lookup logic — use
_get_and_validate_existing_key()
update_key_fn() contains a hand-rolled copy of exactly the same token-hashing + find_unique + 404-raise pattern that _get_and_validate_existing_key() was just updated to provide. _process_single_key_update() (used by the bulk-update path) already delegates to that helper correctly.
Since _get_and_validate_existing_key() accepts (token, prisma_client), this block can be replaced by a single call to that helper. Having two divergent copies means any future change (e.g. to error message, hash logic, or audit logging) must be made in both places, risking drift.
| from litellm.proxy.proxy_server import hash_token | ||
|
|
||
| if token.startswith("sk-"): | ||
| hashed_token = hash_token(token=token) | ||
| else: | ||
| hashed_token = token |
There was a problem hiding this comment.
Inline import and manual hashing — use existing helper
The helper _hash_token_if_needed (imported at line 79 from litellm.proxy.utils) already encapsulates the same conditional-hashing guard used here. Replacing this block with a call to that helper avoids the deferred from litellm.proxy.proxy_server import hash_token import and removes duplicated logic.
The same pattern also appears in update_key_fn() at lines 2122–2127 and should be consolidated as well.
…add test - Deduplicate: update_key_fn now delegates to _get_and_validate_existing_key() instead of inlining its own copy of the lookup logic - Use _hash_token_if_needed (already imported at module level) instead of inline `from proxy_server import hash_token` + manual conditional - Fix stale docstring: _get_and_validate_existing_key raises ProxyException, not HTTPException - Add unit test: test_update_key_nonexistent_key_returns_404 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace prisma_client.get_data() with direct find_unique() in _get_and_validate_existing_key() and update_key_fn() - Fix incorrect error message from 'Team not found' to 'Key not found' Fixes BerriAI#24063
71cdd91
into
litellm_yj_march_18_2026
Summary
Failure Path (Before Fix)
/key/updatereturns401for a nonexistent bodykeyeven when theAuthorizationheader contains a valid master key. This happens becauseprisma_client.get_data()treats the token as an auth credential and raises a 401 when it doesn't exist in the DB.Fix
Replace
prisma_client.get_data()with direct Prismafind_unique()in both_get_and_validate_existing_key()andupdate_key_fn(), matching the pattern used in the/key/blockand/key/unblockfix (PR #23977). Also fixes an incorrect "Team not found" error message inupdate_key_fn.Testing
Type
🐛 Bug Fix