mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
fix(spend-tracking): tie the already-hashed pass-through to provenance
The hashed-jwt branch trusted the value's shape alone, so a caller-supplied key in that shape was stored unhashed. Both pass-throughs now require the value to match the auth-time user_api_key_hash, and the shape check is a full match.
This commit is contained in:
parent
9697748f92
commit
fb417a5563
2 changed files with 31 additions and 8 deletions
|
|
@ -64,7 +64,11 @@ def _is_master_key(api_key: str | None, _master_key: str | None) -> bool:
|
|||
return secrets.compare_digest(api_key, _master_key)
|
||||
|
||||
|
||||
_HASHED_JWT_RE = re.compile(r"^hashed-jwt-[a-fA-F0-9]{64}$")
|
||||
_HASHED_JWT_RE = re.compile(r"hashed-jwt-[a-fA-F0-9]{64}")
|
||||
|
||||
|
||||
def _is_prehashed_key_shape(value: str) -> bool:
|
||||
return is_valid_sha256_hash(value) or _HASHED_JWT_RE.fullmatch(value) is not None
|
||||
|
||||
|
||||
def _redact_logged_api_key(value: str | None, *, already_hashed: bool = False) -> str | None:
|
||||
|
|
@ -73,9 +77,7 @@ def _redact_logged_api_key(value: str | None, *, already_hashed: bool = False) -
|
|||
stripped: Final = re.sub(r"(?i)^bearer ", "", value)
|
||||
if not stripped:
|
||||
return None
|
||||
if already_hashed and is_valid_sha256_hash(stripped):
|
||||
return stripped
|
||||
if _HASHED_JWT_RE.match(stripped):
|
||||
if already_hashed and _is_prehashed_key_shape(stripped):
|
||||
return stripped
|
||||
return hash_token(stripped)
|
||||
|
||||
|
|
@ -136,7 +138,7 @@ def _get_spend_logs_metadata(
|
|||
_raw_key: Final = clean_metadata.get("user_api_key")
|
||||
_trusted_hash: Final = metadata.get("user_api_key_hash")
|
||||
_already_hashed: Final = (
|
||||
isinstance(_trusted_hash, str) and is_valid_sha256_hash(_trusted_hash) and _trusted_hash == _raw_key
|
||||
isinstance(_trusted_hash, str) and _is_prehashed_key_shape(_trusted_hash) and _trusted_hash == _raw_key
|
||||
)
|
||||
clean_metadata["user_api_key"] = _redact_logged_api_key(_raw_key, already_hashed=_already_hashed)
|
||||
clean_metadata["applied_guardrails"] = applied_guardrails
|
||||
|
|
@ -296,7 +298,7 @@ def get_logging_payload(kwargs, response_obj, start_time, end_time) -> SpendLogs
|
|||
standard_logging_total_tokens = standard_logging_payload.get("total_tokens", 0)
|
||||
_trusted_hash = metadata.get("user_api_key_hash")
|
||||
_key_already_hashed = (
|
||||
isinstance(_trusted_hash, str) and is_valid_sha256_hash(_trusted_hash) and _trusted_hash == api_key
|
||||
isinstance(_trusted_hash, str) and _is_prehashed_key_shape(_trusted_hash) and _trusted_hash == api_key
|
||||
)
|
||||
api_key = _redact_logged_api_key(api_key, already_hashed=_key_already_hashed) or ""
|
||||
|
||||
|
|
|
|||
|
|
@ -2653,10 +2653,24 @@ def test_redact_logged_api_key_long_opaque_token_is_hashed():
|
|||
|
||||
def test_redact_logged_api_key_hashed_jwt_passes_through():
|
||||
jwt_hash = "hashed-jwt-" + "a" * 64
|
||||
result = _redact_logged_api_key(jwt_hash)
|
||||
result = _redact_logged_api_key(jwt_hash, already_hashed=True)
|
||||
assert result == jwt_hash
|
||||
|
||||
|
||||
def test_redact_logged_api_key_hashed_jwt_shape_without_provenance_is_hashed():
|
||||
lookalike = "hashed-jwt-" + "a" * 64
|
||||
result = _redact_logged_api_key(lookalike)
|
||||
assert result == hash_token(lookalike)
|
||||
assert result != lookalike
|
||||
|
||||
|
||||
def test_redact_logged_api_key_hashed_jwt_trailing_newline_is_hashed():
|
||||
trailing = "hashed-jwt-" + "a" * 64 + "\n"
|
||||
result = _redact_logged_api_key(trailing, already_hashed=True)
|
||||
assert result == hash_token(trailing)
|
||||
assert result != trailing
|
||||
|
||||
|
||||
def test_redact_logged_api_key_hashed_jwt_short_suffix_is_hashed():
|
||||
short_jwt = "hashed-jwt-tooshort"
|
||||
result = _redact_logged_api_key(short_jwt)
|
||||
|
|
@ -2730,10 +2744,17 @@ def test_get_spend_logs_metadata_provenance_bypass_requires_hash_match():
|
|||
|
||||
def test_get_spend_logs_metadata_hashed_jwt_unchanged():
|
||||
jwt_hash = "hashed-jwt-" + "b" * 64
|
||||
meta = _get_spend_logs_metadata({"user_api_key": jwt_hash})
|
||||
meta = _get_spend_logs_metadata({"user_api_key": jwt_hash, "user_api_key_hash": jwt_hash})
|
||||
assert meta["user_api_key"] == jwt_hash
|
||||
|
||||
|
||||
def test_get_spend_logs_metadata_hashed_jwt_shape_without_provenance_is_hashed():
|
||||
lookalike = "hashed-jwt-" + "b" * 64
|
||||
meta = _get_spend_logs_metadata({"user_api_key": lookalike})
|
||||
assert meta["user_api_key"] == hash_token(lookalike)
|
||||
assert meta["user_api_key"] != lookalike
|
||||
|
||||
|
||||
def test_get_spend_logs_metadata_none_key_is_none():
|
||||
meta = _get_spend_logs_metadata({"user_api_key": None})
|
||||
assert meta["user_api_key"] is None
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue