Retroactively author 15 grouped detail docs covering the backend functional (feat/fix) commits made before the change ledger was established (fad48ff), closing the ledger's detail-doc axis for the pre-convention history. Each doc groups a feature's constituent commits, lists their SHAs with subjects, and carries a backfill note stating it was reconstructed from git history on 2026-07-07 and not independently re-verified (current tree green at9911b8c). Add a Detail docs section to INDEX.md linking every detail doc (the 6 existing + 15 backfill) to the commit(s) it covers, so a doc is findable from the index without a column on the auto-generated ledger table. Catch the table up with the missing9911b8crow. Scope: backend (Go/Java/K8s) only, per the ledger's stated convention that frontend/panel commits are the collaborator's UI work; non-functional commits (docs/style/chore/refactor) keep their table row without a dedicated detail doc.
2.6 KiB
Passkey (WebAuthn) enrollment subsystem + hardening (ledger backfill)
- Type: feature + fix — retroactive ledger entry
- Date: 2026-07-01 – 2026-07-02
- Area:
internal/passkey(go-webauthn adapter),internal/api(enrollment handlers/audit),internal/store(migrations 0007–0009) - Commits:
f2c916dfeat(api): passkey enrollment persistence layer742f15ffeat(api): passkey enrollment endpoints0261204feat(passkey): go-webauthn enrollment verifier adapter (Oracle-verified against a virtual authenticator)fce0fcefeat(passkey): wire the enrollment verifier into felis-api7278cd7feat(passkey): require + record user verification at enrollment (UserVerification=required; captureuser_verified/backup_eligible/backup_state— migration 0009) — fix (d)cdbb5abfix(api): record credential id in the passkey-register audit event so bind/unbind are symmetric — fix (a)9953275fix(api): boundwebauthn_challengesgrowth by superseding all prior rows per (user, purpose) — fix (b)20e31fbfix(store): cascade-delete passkeys + challenges on user removal (recreate both FKsON DELETE CASCADE, scoped to the passkey tables only) — fix (c)54bc6effix(api): clear bound passkeys on password change to close a takeover foothold — fix (e)
- Tasks: #36 (passkey bind with email-OTP fallback), #48–#52 (fixes a–e)
What it did
Built the WebAuthn enrollment half — persistence, the go-webauthn crypto adapter, and the register-begin/finish endpoints — then hardened it through the five-fix batch (a–e): symmetric audit, a bounded challenge table, cascade cleanup, enforced+recorded user verification, and unbinding every passkey on a password reset so a passkey planted through a transiently-hijacked session cannot survive as a standing login foothold.
Why
Passkeys are the phishing-resistant factor with email-OTP as the fallback. The hardening batch closes the seams that make enrollment safe to rely on: without UV enforcement a passkey proves possession but not user; without the password-reset clear, a planted passkey outlives the very remediation meant to evict an attacker.
Backfill note. Reconstructed 2026-07-07 from the commit history. The adapter crypto was verified against a virtual authenticator (virtualwebauthn), and each fix shipped with a targeted test (UV-negative rejection, challenge-growth bound, cascade, symmetric audit) at its commit. Not independently re-verified for this doc; current tree green at
9911b8c. The assertion/login half is a separate doc (passkey-login).