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:
mateo-berri 2026-08-20 22:38:04 -07:00
parent 9697748f92
commit fb417a5563
2 changed files with 31 additions and 8 deletions

View file

@ -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 ""

View file

@ -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