fix(revocation): sign status list with the same key URL as the credential - #79
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug (correctness, prod)
sign-status-list.mjshard-coded the status list JWTkidunder 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 →checkRevocationreturnschecked:false→ the badge falls through to VALID withstatusUnknown.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
kidthroughkeyUrl(kid)— the same helper/base as issuance — so the two signed artifacts can't diverge again.status/1is regenerated on every emit, so this fixes revocation for all badges with no per-credential re-signing.statusUnknown) renders as a cautious "révocation non vérifiée" (warn), never a plain "valide".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