Skip to content

fix(revocation): sign status list with the same key URL as the credential - #79

Merged
blafourcade merged 2 commits into
mainfrom
fix/status-list-kid
Jul 24, 2026
Merged

fix(revocation): sign status list with the same key URL as the credential#79
blafourcade merged 2 commits into
mainfrom
fix/status-list-kid

Conversation

@blafourcade

Copy link
Copy Markdown
Contributor

Bug (correctness, prod)

sign-status-list.mjs hard-coded the status list JWT kid under SITE_BASE (verify.ai-driven-dev.fr), but the public key is only published under DATA_BASE (Pages). A verifier fetching the status list's key from SITE_BASE fails → checkRevocation returns checked:false → the badge falls through to VALID with statusUnknown.

Consequence: a REVOKED (RGPD-removed) badge would read "Certificat valide" in prod. The local single-host harness structurally couldn't catch this (it forces one host).

Fix

  • Route the status list kid through keyUrl(kid) — the same helper/base as issuance — so the two signed artifacts can't diverge again. status/1 is regenerated on every emit, so this fixes revocation for all badges with no per-credential re-signing.
  • Fail-safe in the reference verify page: a valid signature whose revocation is unverifiable (statusUnknown) renders as a cautious "révocation non vérifiée" (warn), never a plain "valide".
  • Regression test (revocation-e2e) + a full lifecycle E2E (lifecycle-e2e) covering: form→record→issuance→VALID/EXPIRED/NOT_YET_VALID/REVOKED, RGPD removal (out of directory + exported proof revoked + no personal data), tampered→INVALID, status-unreachable→fail-safe, form-validation rejects.

118 tests, 0 fail.

🤖 Generated with Claude Code

blafourcade and others added 2 commits July 24, 2026 14:27
…tial

sign-status-list hard-coded the status list's `kid` under SITE_BASE
(verify.ai-driven-dev.fr), but the public key is only published under DATA_BASE
(Pages). A verifier fetching the status list's key from SITE_BASE fails, so
checkRevocation returns checked:false and the badge falls through to VALID with
statusUnknown — a REVOKED (RGPD-removed) badge would read "valide" in prod.

- Route the status list kid through keyUrl(kid), the same helper/base as
  issuance, so the two signed artifacts can't diverge again. status/1 is
  regenerated on every emit, so this fixes revocation for all badges with no
  per-credential re-signing.
- Fail-safe in the reference verify page: a VALID signature whose revocation
  is unverifiable (statusUnknown) renders as a cautious "révocation non
  vérifiée" (warn), never a plain "valide".
- Add an end-to-end regression test: signing status + credential with the same
  helper detects REVOKED; a status list kid on another host reproduces the
  statusUnknown fail-open the local single-host harness couldn't catch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PC9TRnYr5AXe7GmBmHjkBy
Drive the real functions through the whole journey (the GitHub workflows —
events/Apps — aren't locally runnable, but the logic they wrap is): filled
form -> member record (identity = issue author) -> signed issuance -> display
VALID; then EXPIRED, NOT_YET_VALID, REVOKED, RGPD removal (out of directory +
exported proof reads revoked + no personal data), tampered -> INVALID, status
unreachable -> fail-safe (never a plain "valide"), and form validation rejects.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PC9TRnYr5AXe7GmBmHjkBy
@blafourcade
blafourcade requested review from a team as code owners July 24, 2026 14:16
@blafourcade
blafourcade merged commit 527528e into main Jul 24, 2026
2 checks passed
@blafourcade
blafourcade deleted the fix/status-list-kid branch July 24, 2026 19:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant