Summary
Three defects in the jti replay-registry lifecycle, one coherent fix. All CONFIRMED BY EXECUTION in the 2026-08-23 audit of v0.2.0 @ 571073c (additions posted on #18).
1. The issuing service can never verify its own attestations
sign_verdict() registers the jti at signing (crypto.py:361); verify_attestation() checks-and-registers the same jti at verification (crypto.py:462) — against the same per-instance registry. Executed: sign a token, immediately verify it on the same A2ACryptoService → False, "Replay detected: jti already seen". Every self-verification of a freshly issued token fails. Issuance and consumption need separate semantics (drop sign-time registration, or use distinct issuance/consumption registries).
2. Replay protection is per-process — multi-worker replay succeeds
JtiRegistry is in-memory per A2ACryptoService instance (crypto.py:73-123). Executed: token issued via worker-1's service verifies first-time on a fresh worker-2 service → accepted. Any horizontally scaled deployment (or restart, pre-persistence) has replay windows across workers. Same fix class as qwed-verification's multi-worker state findings: shared store (Redis et al.) or accept-and-document single-process scope.
3. Issuance ignores the dedupe result — contradictory same-jti JWTs
sign_verdict calls check_and_register(trace_id) and discards the return value. Executed: two calls with the same trace_id mint two distinct, cryptographically valid JWTs sharing one jti with different verdict claims (forwarded vs blocked). RFC 7519 jti uniqueness is violated at the source; whichever token a consumer sees first poisons the registry for the other. Fix: raise or refuse on duplicate trace_id at issuance (ties into #18's caller-controlled trace_id).
Remediation shape
One change to the registry contract: issuance registers with "seen-locally" semantics that don't poison verification, verification registers consumption, duplicates at issuance raise, and the store is shared across workers.
Summary
Three defects in the jti replay-registry lifecycle, one coherent fix. All CONFIRMED BY EXECUTION in the 2026-08-23 audit of v0.2.0 @
571073c(additions posted on #18).1. The issuing service can never verify its own attestations
sign_verdict()registers the jti at signing (crypto.py:361);verify_attestation()checks-and-registers the same jti at verification (crypto.py:462) — against the same per-instance registry. Executed: sign a token, immediately verify it on the sameA2ACryptoService→False, "Replay detected: jti already seen". Every self-verification of a freshly issued token fails. Issuance and consumption need separate semantics (drop sign-time registration, or use distinct issuance/consumption registries).2. Replay protection is per-process — multi-worker replay succeeds
JtiRegistryis in-memory perA2ACryptoServiceinstance (crypto.py:73-123). Executed: token issued via worker-1's service verifies first-time on a fresh worker-2 service → accepted. Any horizontally scaled deployment (or restart, pre-persistence) has replay windows across workers. Same fix class as qwed-verification's multi-worker state findings: shared store (Redis et al.) or accept-and-document single-process scope.3. Issuance ignores the dedupe result — contradictory same-jti JWTs
sign_verdictcallscheck_and_register(trace_id)and discards the return value. Executed: two calls with the same trace_id mint two distinct, cryptographically valid JWTs sharing one jti with different verdict claims (forwardedvsblocked). RFC 7519 jti uniqueness is violated at the source; whichever token a consumer sees first poisons the registry for the other. Fix: raise or refuse on duplicate trace_id at issuance (ties into #18's caller-controlled trace_id).Remediation shape
One change to the registry contract: issuance registers with "seen-locally" semantics that don't poison verification, verification registers consumption, duplicates at issuance raise, and the store is shared across workers.