mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-18 04:44:37 +00:00
* fix(auth): return a generic 401 for an unknown key The wrong-key 401 shipped the internal verification table name and the submitted key's full SHA-256 hash to any unauthenticated caller. Both now go to the proxy's own log and the response carries only "Invalid API key provided." The Admin UI detected a dead session by string-matching that message, so it now reads the structured error type instead. * Update ui/litellm-dashboard/src/lib/http/client.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * fix(auth): log the missing key hash where every resolve path passes Review catch: JWT key-mapping misses resolve through IdentityStore without touching the main key path, so logging the hash there left those callers with nothing but the generic 401. Log it at the store's raise site instead, which every resolve path shares, and drop the now-duplicate log. * test(auth): inject a key-less client instead of patching the DB fetch The store test tripped TQ008 by patching _fetch_key_object_from_db_with_reconnect. Hand IdentityStore a client whose get_data returns no row instead, so the miss is driven through the real lookup rather than around it. * fix(auth): log the unknown key's hash once on the auth failure line The per-lookup warnings in get_key_object and IdentityStore logged whatever value they were handed, which on the failure-logging path is the raw key a caller sent (a provider key included), let a newline in ?key= forge log lines, and fired on lookups that expect a miss. The hash now rides KeyNotFoundError onto the single existing auth failure log line next to the requester IP, with control characters escaped. Also drops the new comments and updates skipped tests that quoted the old message. * test(auth): add the blank lines ruff format expects before _InFlightCountingPrisma * test(integration): expect the generic wrong-key 401 in the management lifecycle tests * fix(auth): keep the operator wording on the unknown-key log line and drop the redundant 401 rewrite * test(auth): cover the generic unknown-key 401 across routes, proxies, and a worker kill * test(auth): cover the realtime upgrade refusing an unknown key without echoing it --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
48 lines
1.7 KiB
Python
48 lines
1.7 KiB
Python
from __future__ import annotations
|
|
|
|
from litellm.constants import INVALID_API_KEY_ERROR_MESSAGE
|
|
from litellm.proxy._types import ProxyErrorTypes, ProxyException
|
|
from litellm.proxy.auth.resolvers.exceptions import (
|
|
IdentityResolutionError,
|
|
KeyNotFoundError,
|
|
KeyNotInCacheError,
|
|
NoDatabaseConnectionError,
|
|
PrincipalMissingSourceKeyError,
|
|
)
|
|
|
|
|
|
def test_all_resolution_errors_share_one_base():
|
|
errors = [
|
|
NoDatabaseConnectionError(),
|
|
KeyNotInCacheError("hashed"),
|
|
KeyNotFoundError("hashed"),
|
|
PrincipalMissingSourceKeyError(),
|
|
]
|
|
assert all(isinstance(e, IdentityResolutionError) for e in errors)
|
|
|
|
|
|
def test_key_not_found_preserves_the_public_401_contract():
|
|
# The auth seam catches ProxyException and rewrites the 401 message, so a
|
|
# missing key must keep mapping to that exact contract.
|
|
error = KeyNotFoundError("hashed-token")
|
|
|
|
assert isinstance(error, ProxyException)
|
|
assert error.code == "401"
|
|
assert error.type == ProxyErrorTypes.token_not_found_in_db.value
|
|
assert error.param == "key"
|
|
|
|
|
|
def test_key_not_found_message_hides_the_hash_but_the_raise_site_can_still_log_it():
|
|
"""The JWT key-mapping paths surface this exception's own message to the caller
|
|
without the rewrite the main key path applies, so the message itself has to be
|
|
generic. The hash stays reachable on the instance for the auth failure log line."""
|
|
error = KeyNotFoundError("hashed-token")
|
|
|
|
assert error.message == INVALID_API_KEY_ERROR_MESSAGE
|
|
assert "hashed-token" not in str(error)
|
|
assert "/key/generate" not in str(error)
|
|
assert error.hashed_token == "hashed-token"
|
|
|
|
|
|
def test_key_not_in_cache_names_the_token():
|
|
assert "hashed-token" in str(KeyNotInCacheError("hashed-token"))
|