# Tasks — `@suaveplan/webauthn` (WebAuthn Level 3 client + parser + verifier) > Every checkbox MUST reflect work actually done in this change. Do not retroactively tick boxes for work that was already in main when this change opened. Do not tick a box for a test that doesn't exist or doesn't pin the requirement it claims to verify. ## 0. Blocking prerequisite - [ ] 0.1 `enterprise-cbor` change archived and `@suaveplan/cbor` published from genesis (COSE / CTAP CBOR profile available). **No implementation task in §2 may start until this lands.** Spec / proposal / fixture-prep tasks (§1, §3) are unblocked. ## 1. Spec authoring - [x] 1.1 Read `../suaveplan/enterprise/webauthn.md` end-to-end - [x] 1.2 Read W3C WebAuthn L3 §5, §6, §7, §8, §9, §10 - [x] 1.3 Read CTAP 2.1 §6.5, RFC 9052 §7, IANA WebAuthn registries - [x] 1.4 Read `openspec/AGENTS.md` §13 and §14 - [x] 1.5 Draft `proposal.md` from `openspec/templates/proposal.md` - [x] 1.6 Fill in `## Source mapping` table — every requirement cites enterprise-doc lines + a W3C / CTAP / RFC anchor - [x] 1.7 Draft `specs/webauthn/spec.md` from `openspec/templates/spec-delta.md` - [x] 1.8 Every requirement in the spec delta carries a `**Verified by:**` line naming concrete `it(...)` titles ## 2. Implementation ### 2.1 Adapter abstraction - [x] 2.1.1 `src/adapter/adapter.ts` — `WebAuthnAdapter` interface (`create`, `get`, `isSupported`, `isConditionalMediationAvailable`) - [x] 2.1.2 `src/adapter/default-adapter.ts` — `defaultWebAuthnAdapter()` binding `globalThis.navigator.credentials`; throws `WEBAUTHN.NOT_SUPPORTED` when missing - [x] 2.1.3 `src/adapter/dom-exception-mapper.ts` — `NotAllowedError` → `WEBAUTHN.USER_CANCELLED`, `InvalidStateError` → `WEBAUTHN.INVALID_STATE`, `SecurityError` → `WEBAUTHN.SECURITY_ERROR`, `AbortError` → `WEBAUTHN.ABORTED`, `NotSupportedError` → `WEBAUTHN.NOT_SUPPORTED` ### 2.2 Errors + observability - [x] 2.2.1 `src/errors/errors.ts` — `WebAuthnError` registry via `defineCodes("WEBAUTHN", [...])` covering every code in the spec delta - [x] 2.2.2 `src/observability/metrics.ts` — `webauthn.register_duration_ms`, `webauthn.authenticate_duration_ms`, `webauthn.outcome_total`, `webauthn.attestation_format_total`, `webauthn.errors_total` ### 2.3 Parser (universal-tier `./parser` subpath) - [x] 2.3.1 `src/parser/client-data.ts` — `parseClientDataJSON`; type pinning + origin + base64url challenge decode + Zod `ClientDataJSONSchema` - [x] 2.3.2 `src/parser/authenticator-data.ts` — `parseAuthenticatorData`; 37-byte fixed-prefix split + flag-byte decoder + attested-credential-data + extensions block - [x] 2.3.3 `src/parser/cose-key.ts` — RFC 9052 §7 COSE_Key decoder consuming `@suaveplan/cbor`; supports ES256 / ES384 / ES512 / EdDSA / RS256 alg labels - [x] 2.3.4 `src/parser/attestation-object.ts` — `parseAttestationObject` per CTAP 2.1 §6.5; extracts `fmt` / `authData` / `attStmt` - [x] 2.3.5 Zod boundary schemas at every export — `MALFORMED_RESPONSE` on mismatch ### 2.4 Verifier (universal-tier `./verifier` subpath) - [x] 2.4.1 `src/verifier/verify-assertion.ts` — signature verify over `authData || sha256(clientDataJSON)` via `@suaveplan/crypto-curve`, dispatched on COSE alg - [x] 2.4.2 `src/verifier/sign-counter.ts` — sign-counter regression detection per W3C L3 §6.1.1 - [x] 2.4.3 `src/verifier/verify-attestation.ts` — `verifyAttestation` dispatcher - [x] 2.4.4 `src/verifier/registry.ts` — `defaultAttestationRegistry()` + `registerAttestationFormat(name, verifier)` ### 2.5 Per-format attestation verifiers - [x] 2.5.1 `src/attestation/packed/packed.ts` — W3C L3 §8.2 - [x] 2.5.2 `src/attestation/fido-u2f/fido-u2f.ts` — W3C L3 §8.6 - [x] 2.5.3 `src/attestation/tpm/tpm.ts` — W3C L3 §8.3 - [x] 2.5.4 `src/attestation/android-key/android-key.ts` — W3C L3 §8.4 - [x] 2.5.5 `src/attestation/android-safetynet/android-safetynet.ts` — W3C L3 §8.5 - [x] 2.5.6 `src/attestation/apple/apple.ts` — W3C L3 §8.8 - [x] 2.5.7 `src/attestation/none/none.ts` — W3C L3 §8.7 ### 2.6 Registration (browser-tier root) - [x] 2.6.1 `src/register/register.ts` — `register(opts)` building `PublicKeyCredentialCreationOptions`, calling adapter, parsing `AuthenticatorAttestationResponse` - [x] 2.6.2 Client-data validator wired (type / origin / challenge constant-time-equals) - [x] 2.6.3 `expectedRpIdHash` constant-time compare - [x] 2.6.4 AbortSignal forwarded to `navigator.credentials.create` - [x] 2.6.5 OTel `webauthn.register_duration_ms` recorded with `outcome` label ### 2.7 Authentication (browser-tier root) - [x] 2.7.1 `src/authenticate/authenticate.ts` — `authenticate(opts)` building `PublicKeyCredentialRequestOptions`, calling adapter, parsing `AuthenticatorAssertionResponse` - [x] 2.7.2 Client-data validator pinned to `webauthn.get` - [x] 2.7.3 Conditional mediation passthrough; `isConditionalMediationAvailable()` helper - [x] 2.7.4 AbortSignal forwarded to `navigator.credentials.get` - [x] 2.7.5 OTel `webauthn.authenticate_duration_ms` recorded with `outcome` label ### 2.8 Extensions - [x] 2.8.1 `src/extensions/prf/prf.ts` — PRF eval input on register + authenticate; result parsing; `enabled === false` fallback - [x] 2.8.2 `src/extensions/large-blob/large-blob.ts` — `support` on register; `read` / `write` on authenticate; result shape ### 2.9 Root barrel + subpath exports - [x] 2.9.1 `src/index.ts` — explicit named re-exports (no `export *`) - [x] 2.9.2 `package.json` `exports` map covers every subpath listed in the proposal - [x] 2.9.3 `package.json` `browser` field set; `engines` does not pin Node-only ## 3. Test-vector bundle - [ ] 3.1 `src/__fixtures__/yubikey-packed/` — Yubikey 5 packed attestation good vector + corrupted variants - [ ] 3.2 `src/__fixtures__/yubikey-fido-u2f/` — Yubikey 5 fido-u2f good vector - [ ] 3.3 `src/__fixtures__/windows-hello-tpm/` — Windows Hello TPM attestation good vector - [ ] 3.4 `src/__fixtures__/macos-touch-id-apple/` — Apple anonymous attestation good vector - [ ] 3.5 `src/__fixtures__/chrome-software-none/` — Chrome software authenticator `none` format - [ ] 3.6 `src/__fixtures__/android-key/` and `android-safetynet/` — Pixel platform-authenticator vectors - [ ] 3.7 Each fixture set has at least one corrupted variant (signature flipped, rpIdHash zeroed, signCount frozen) - [ ] 3.8 Fixtures generated offline; CI does not depend on a real authenticator ## 4. Tests (runner-neutral imports per CLAUDE.md §11 — `@suaveplan/testing/runner`) - [ ] 4.1 `src/register/register.test.ts` — happy path, client-data mismatches, AbortSignal, DOMException mapping - [ ] 4.2 `src/authenticate/authenticate.test.ts` — happy path, conditional mediation, AbortSignal - [ ] 4.3 `src/parser/authenticator-data.test.ts` — every flag combination, attested-credential-data, extensions block - [ ] 4.4 `src/parser/attestation-object.test.ts` — `fmt` / `authData` / `attStmt` extraction - [ ] 4.5 `src/parser/client-data.test.ts` — type pinning, base64url decode - [ ] 4.6 `src/parser/cose-key.test.ts` — RFC 9052 §7 round-trip per alg - [ ] 4.7 `src/verifier/verify-assertion.test.ts` — every alg, signature-flip negative, sign-counter regression boundaries - [ ] 4.8 `src/verifier/verify-attestation.test.ts` — registry dispatch, unknown-format error - [ ] 4.9 Per-format `src/attestation//.test.ts` — good + corrupted vectors for `packed`, `fido-u2f`, `tpm`, `android-key`, `android-safetynet`, `apple`, `none` - [ ] 4.10 `src/extensions/prf/prf.test.ts` — register + authenticate eval round-trip + length assertion + missing-PRF fallback - [ ] 4.11 `src/extensions/large-blob/large-blob.test.ts` — support flag, read, write - [ ] 4.12 `src/adapter/adapter.test.ts` — `NOT_SUPPORTED` when missing, conditional-mediation check - [ ] 4.13 `src/errors/errors.test.ts` — every code thrown by ≥1 production path; DOMException mapping - [ ] 4.14 `src/observability/metrics.test.ts` — every histogram + counter recorded against in-memory `MeterProvider` - [ ] 4.15 `src/parser/authenticator-data.property.test.ts` — fast-check fuzz, parser never panics - [ ] 4.16 `src/parser/attestation-object.property.test.ts` — fast-check fuzz of envelope decoder - [ ] 4.17 `src/index.test.ts` — every documented subpath resolves - [ ] 4.18 `tests/dep-graph.test.ts` — `./parser` and `./verifier` import zero DOM types - [ ] 4.19 Every `it(...)` carries `{ timeout: 5000 }` (or 30000 for genuinely async) ## 5. Documentation - [x] 5.1 Sibling `.md` for every implementation `.ts` (≥200 words, basic + advanced examples, full API reference) - [x] 5.2 README.md with Modules table linking every co-located `.md` - [x] 5.3 Update `CLAUDE.md` package listing under the Vault section - [x] 5.4 README cites W3C WebAuthn L3 / CTAP 2.1 / RFC 9052 anchors ## 6. Benchmarks (mitata) - [ ] 6.1 `src/__benchmarks__/parse_authdata.bench.ts` - [ ] 6.2 `src/__benchmarks__/verify_assertion_es256.bench.ts` - [ ] 6.3 `src/__benchmarks__/verify_attestation_packed.bench.ts` - [ ] 6.4 `src/__benchmarks__/verify_attestation_tpm.bench.ts` - [ ] 6.5 Baseline numbers committed in README ## 7. Pre-archive verification (the 5-gate triple) - [ ] 7.1 `bun run check` — Biome 0 errors / 0 warnings / 0 info across the whole monorepo - [ ] 7.2 `bunx turbo lint typecheck test --filter=@suaveplan/webauthn` — green - [ ] 7.3 `bunx turbo test:coverage --filter=@suaveplan/webauthn` — 100/100/100/100 - [ ] 7.4 `bun scripts/openspec-conformance.ts @suaveplan/webauthn` — exit 0, no `error` findings - [ ] 7.5 `bunx turbo build --filter=@suaveplan/webauthn` — green ## 8. Pre-archive 7-row checklist (AGENTS.md §13) - [ ] 8.1 Generics threaded; `expectTypeOf` assertions on every literal-type claim (`AttestationType`, `WebAuthnErrorCode`, `CoseAlg`) - [ ] 8.2 Zod schemas wired at every declared boundary (register / authenticate options, parsed structures, COSE_Key) - [ ] 8.3 Every error code in `errors.ts` thrown by at least one production path - [ ] 8.4 Strict-mode behaviors tested both on and off (UV required vs preferred; attestation `direct` vs `none`) - [ ] 8.5 OTel instruments emitted from production paths and asserted with a real in-memory `MeterProvider` - [ ] 8.6 Lifecycle dedup tested where claimed (AbortSignal mid-flight, conditional-mediation availability cache) - [ ] 8.7 No retroactive tick — every box above reflects work done in this change ## 9. Banned-pattern + barrel + runner-import scans - [ ] 9.1 `grep -rn "@deprecated\|TODO\|FIXME\|ts-ignore\|ts-expect-error\|\.skip\|\.only" packages/vault/webauthn/src/ --include="*.ts"` — zero output - [ ] 9.2 `grep -rn "^\s*export\s\+\(type\s\+\)\?\*\s\+from" packages/vault/webauthn/src/ --include="index.ts"` — zero output (root `src/index.ts` permitted; feature barrels must use named exports) - [ ] 9.3 `grep -rnE "from ['\"](vitest|bun:test)['\"]" packages/vault/webauthn/src/ --include="*.ts" --include="*.tsx"` — zero output (package is not on the testing-infra exempt allowlist) ## 10. Archive - [ ] 10.1 Fold spec delta into `openspec/specs/webauthn/spec.md` - [ ] 10.2 Move `openspec/changes/enterprise-webauthn/` → `openspec/archive/-enterprise-webauthn/` - [ ] 10.3 Refresh `MANIFEST.md` via `bun scripts/folder-reorg/generate-manifest.ts` - [ ] 10.4 Refresh `MISSING.yml` via `bun scripts/generate-missing.ts` - [ ] 10.5 Move `@suaveplan/webauthn` row in `STATUS.md` from `Pending` → `Completed` - [ ] 10.6 Update `SUMMARY.md` with per-package paragraph - [ ] 10.7 `bunx markdownlint-cli2 --fix` and stage results