test-coverage-analysis.md
peach-browser — Test Coverage Analysis
Date: 2026-06-11
Base: dev lane, HEAD 7100d7d25214 (v3.9.8)
Method: Full read of src/ (130 source modules, 43,826 LOC) cross-referenced against the test suite (109 test files).
Mode: Read-only analysis. No source modified.
Test count: 1,632 literal it()/test() declarations (1,062 unit/component in src/, 570 Playwright e2e in tests/). Many suites are parameterized via it.each/loops (e.g. content.adversarial, vault.adversarial, codex.adversarial each declare "≥200 cases"), so the executed assertion count recorded in LOG.md for the v3.9.x line is ~3,200. Headline ratio: ~1,632 declared tests against ~44k LOC ≈ 1 test per 27 LOC — high by volume, but unevenly distributed (see §3, §5).
Section 1 — Module Inventory
Background (src/background/) — service worker, vault, sync, passkeys
| Module | Lines | Purpose |
|---|---|---|
| vault.ts | 2890 | Core vault: unlock/lock, AES-GCM crypto, entry CRUD, password history, soft delete/trash, duress vault, vault-history snapshots, sync merge (applyMergedRemoteEntries), changePassword, recovery escrow, normalizeEntry whitelist |
| messages.ts | 1610 | Message router: GET_ENTRY_FOR_FILL/_EDIT, AUTOFILL_QUERY, GET_RECOVERY_WORDS, destructive-op gating, canary, SAVE_NOTE, sync alerts, license/pro gate |
| passkey.ts | 1413 | WebAuthn interception — Chrome webAuthenticationProxy + Firefox content-proxy create/get ceremonies, approval flow, pending-request tracking |
| passkey-crypto.ts | 370 | WebAuthn crypto: ES256/Ed25519 keypair gen, COSE/CBOR encoding, P1363→DER, authenticator data |
| codex-handlers.ts | 310 | Codex backup restore: verify-code validation, blob decompress, metadata validation before unlock |
| chirp-sync-vault-adapter.ts | 438 | Sync crypto boundary: per-entry AES-GCM key derivation, entry/tombstone apply, device-manifest sign/verify |
| chirp-sync-coordinator.ts | 295 | Manifest-diff sync protocol (HELLO→MANIFEST→REQUEST→ENTRIES→TOMBSTONES→DONE) over Noise session |
| chirp-sync-runner.ts | 181 | Production sync runner: vault adapter + Noise XX transport + IP cache + subnet-probe fallback |
| sync-trigger.ts | 192 | Sync dispatcher: vault-change/foreground/manual/mDNS triggers, per-device runner, trustSource:'direct' filter |
| clipboard.ts | 201 | Clipboard write + auto-clear via chrome.alarms + offscreen (HV-2) |
| ml.ts | 200 | Logistic-regression field classifier (hard-coded weights) |
| alarms.ts | 69 | Auto-lock via chrome.alarms + idle detection |
| paired-devices.ts | 180 | Paired-device registry + last-known-IP cache |
| chirp-identity.ts | 143 | Local device ID (UUID, storage.local) + device name |
| health.ts | 170 | Advanced vault-health analysis (Pro): age/reuse/exposure scoring |
| license.ts | 169 | Ed25519 license signature verification + Pro gate |
| self-test.ts | 110 | Crypto KAT on unlock (PCH-01-020 pinned vector) |
| sync-alerts.ts | 77 | Pending sync-alert queue (HV-1b) |
| pending-vouched-devices.ts | 81 | MV-7 queue for vouched devices awaiting user confirmation |
| commands.ts | 69 | Keyboard-shortcut → summon-inline routing |
| entry.ts | 73 | MV3 lock-on-close port (LV-5 nonce'd port) |
| offscreen.ts | 43 | Offscreen-document clipboard clear (HV-2) |
| recovery-pending.ts | 33 | Transient recovery-confirm flag (suppresses lock-on-close) |
Content / Autofill (src/content/) — the injection layer
| Module | Lines | Purpose |
|---|---|---|
| inject.ts | 4405 | Inline UI rendering: showAutofillSelector, injectFieldIcon, showTOTPSelector, showCardSelector, passkey toasts/gate/offer, remember-site toast, smart-generator drawer, anchor positioning |
| index.ts | 1263 | Autofill orchestration: handleFieldInteraction, 3-layer dismissal model, fill-success lock, focusin/mutation observers, initialScan aggression levels, TOTP/card paths, message routing (SUMMON_AUTOFILL/VAULT_UNLOCKED/passkey), search palette, Firefox passkey bridge |
| detect.ts | 357 | Field/form detection: login/signup/email-first/TOTP/card detection, deep active-input, visibility, CSS.escape (PCH-01-025) |
| passkeys-firefox.ts | 325 | Firefox MAIN-world navigator.credentials interception proxy |
| save.ts | 309 | Save-on-submit detection (offer to save new credentials) |
| content-ml-detector.ts | 190 | Content-side ML field-prediction singleton |
| fill.ts | 136 | Field filling: fillFields (cross-origin-safe), setNativeValue (React), card fill, Luhn/network |
| policy.ts | 130 | Site password-policy extraction (min/max/forbidden chars) |
| passkeys-firefox-relay.ts | 68 | MAIN↔content allowlisted postMessage relay |
| identity-helper.ts | 58 | Saved-identity sort/filter for signup surface |
| interaction.ts | 40 | resolveAutofillInteractionTargets (which fields/whether to open) |
| passkey-toast/* | ~300 | Preact passkey approval toast (inject.tsx, PasskeyToast.tsx, css) |
Crypto (src/crypto/)
| Module | Lines | Purpose |
|---|---|---|
| peachPayload.ts | 1080 | Codex/backup payload: Argon2id deriveKey + NFC, AES-GCM/ChaCha, zstd, BLAKE3 checksum, section encoding, decompression bounds |
| argon2.ts | 52 | Argon2id KDF with NFC normalize + raw fallback |
| zstd.ts | 51 | zstd compress/decompress with output-size cap (MV-4) |
| totp.ts | 40 | TOTP generation + parseOtpauthURI (algorithm/digits/period) |
| peachScript.ts | 64 | PUA (U+E000..E0FF) byte↔char codec |
Shared (src/shared/)
| Module | Lines | Purpose |
|---|---|---|
| import.ts | 605 | Multi-format import (1Password/Bitwarden/ProtonPass/Credential-Exchange/CSV/Peach) |
| types.ts | 543 | Cross-boundary type definitions (no runtime logic) |
| backup.ts | 295 | Peach encrypted backup export/import (canonical NFC raw-retry pattern) |
| codex-types.ts | 288 | Codex payload types + computeParityShards/deriveShardCounts |
| recovery-word-list.ts | 275 | BIP39 2048-word English list |
| chirp-sync-transport.ts | 292 | WebSocket client + Noise XX + subnet probe (rate-limited) |
| chirp-pairing.ts | 291 | Audio pairing driver (chirp exchange → Noise NK) |
| chirp-handshake.ts | 261 | Post-chirp NK + identity frame exchange + SAS derivation |
| noise-xx.ts | 260 | Noise XX mutual-auth handshake (25519/ChaChaPoly/BLAKE2s) |
| sync-identity.ts | 189 | Vault-tied X25519/Ed25519 derivation + vault ID |
| ggwave.ts | 187 | WASM audio-codec loader + ChirpCodec wrapper |
| shortcuts.ts | 185 | Keybinding schema/format/match/conflict |
| recovery-words.ts | 181 | BIP39 recovery (12-word ↔ root key) + HKDF KEK (128-bit prefix) |
| noise-js.ts | 422 | Noise NK handshake + post-handshake HMAC auth proof |
| device-manifest.ts | 221 | Signed device-graph manifest + freshness window |
| chirp-payload.ts | 139 | 64-byte audio chirp payload codec (CRC32) |
| generate.ts | 75 | CSPRNG password generator (rejection sampling, ≤128 chars) |
| favicon.ts | 95 | Favicon URL + fallback initials/colors |
| domain.ts | 49 | eTLD+1 extraction (tldts) + homograph detection (LV-2) |
| browser.ts | 34 | Feature-detected Chrome/Firefox identification |
| passkey-utils.ts | 53 | WebAuthn rpId validation (SEV-1-A) |
| sync-limits.ts / import-limits.ts | 68 | Resource bounds (MV-3/MV-4/HV-1 clamp) |
| Others (ml, storage, config, bridge, native, passkey-flags, local-network) | ~250 | Thin wrappers / constants / types |
Popup UI (src/popup/)
| Module | Lines | Purpose |
|---|---|---|
| Settings.tsx | 2146 | Settings: password change, recovery words, license, feedback, appearance, sync |
| Entry.tsx | 1779 | Entry view/edit: copy, TOTP, inline edit, identity |
| App.tsx | 1707 | Root: routing, vault state, message dispatch |
| Lock.tsx | 1058 | Unlock/create vault, master-password strength, restore options |
| Import.tsx | 912 | Import file parse + mode selection |
| Add.tsx | 764 | Create new entry |
| Vault.tsx | 733 | Vault list, search, sort |
| codex/pdf-import.ts | 703 | Codex PDF → Aztec extraction (MV-5/MV-9/PCH-01-014) |
| CodexRestoreView.tsx | 694 | Codex restore flow (PCH-01-015) |
| Export.tsx / Health.tsx / ChirpSync.tsx | 1576 | Export, health dashboard, sync UI |
| codex/generate.ts | 484 | Codex generation pipeline (compress→erasure→Aztec→PDF) |
| codex/{restore,erasure,aztec,pdf,reed-solomon,helpers}.ts | ~1480 | Codex erasure/restore internals |
| Recovery/Notes/Trash/Panic/Onboarding views | ~1400 | Recovery words, notes editor, trash, panic, onboarding |
| components/{GeneratorDrawer,ChirpPairingFlow,Rune*,Note*}.tsx | ~1700 | Generator drawer, pairing flow, rune canvases, note components |
| chirp-pair.ts / chirp-pair-capacitor.ts | 557 | Pairing UI driver (gateOnSAS) |
Section 2 — Test Suite Inventory
Unit / component tests (src/, Vitest) — 1,062 declared
| Test File | Tests | Covers |
|---|---|---|
| background/vault.test.ts | 76 | Unlock/lock, entry CRUD, password change, duress, biometric unlock, MV-6 whitelist, LV-1 NFC, PCH-01-008/009 |
| background/messages.test.ts | 51 | All message types, MV-1/MV-2, LV-3/LV-4, TOTP strip, pro gate |
| background/messages.adversarial.test.ts | 55 | Origin spoofing, PCH-01-006/007, PCH-01-018 destructive-op gating |
| background/vault.adversarial.test.ts | 44 | Password boundary cases (unicode/emoji/RTL/control/injection), batchAdd invalid |
| background/recovery.adversarial.test.ts | 27 | Recovery word tamper at every position, garbage rejection, password edge cases |
| background/recovery-words.test.ts | 26 | 12-word round-trip, validation, recovery cycles, survives changePassword |
| background/chirp-sync-vault-adapter.test.ts | 23 | MV-3 bounds, MV-7 queue, MV-8d key-rotation, PCH-01-005 separator, entry round-trip |
| background/passkey-chrome-ceremony.test.ts | 23 | Chrome create/get, isUvpaa, locked-vault wait, origin resolution |
| background/vault-history.test.ts | 14 | Snapshots, PCH-01-022 bucket coarsening, dedup |
| background/paired-devices.test.ts | 12 | Upsert/find/remove, IP cache, legacy compat |
| background/passkey-firefox-proxy.test.ts | 12 | Firefox create/get, cross-origin rejection, enumeration protection |
| background/clipboard.test.ts | 11 | HV-2 alarms+offscreen, PCH-01-021 cold-start hash, re-arm |
| background/chirp-sync-runner.test.ts | 11 | Locked check, IP cache, subnet probe, vault-id mismatch |
| background/notes.test.ts | 11 | Note CRUD/sanitization |
| background/sync-trigger.test.ts | 11 | Per-device sync, concurrency guard, MV-7 filter |
| background/chirp-sync-static.test.ts | 9 | Static sync helpers |
| background/health.test.ts | 7 | Reuse/age scoring, hashes-never-stored |
| background/codex-handlers.test.ts | 7 | Pro/locked gates, PCH-01-017 validation |
| background/chirp-sync-coordinator.test.ts | 7 | HELLO, version short-circuit, clock skew, full sync |
| background/passkey-crypto.test.ts | 7 | ES256 keypair, authenticator data, COSE |
| background/alarms.test.ts | 6 | Auto-lock alarm, idle lock |
| background/vault.sync-clamp.test.ts | 6 | HV-1 clamp + REMOTE_PASSWORD_CHANGE/DELETE alerts |
| background/aes.test.ts | 5 | AES-GCM primitives |
| background/chirp-identity.test.ts | 3 | PCH-01-004 deviceId local + migration |
| background/{self-test,passkey-deny-on-close,entry.lock-port,session-escrow}.test.ts | 8 | KAT (PCH-01-020), deny-on-close, LV-5 port, PCH-01-008 escrow |
| crypto/peachPayload.test.ts | 25 | Entry round-trips, AEAD, padding, PCH-01-001 NFC (5 cases), MV-4 |
| crypto/peachScript.test.ts | 24 | 256-byte codec, PUA validation, fuzz corpus |
| crypto/{argon2,totp,crypto,decompression-bomb}.test.ts | 22 | LV-1 NFC parity, TOTP params, MV-4 caps |
| content/content.adversarial.test.ts | 47 | extractDomain boundaries, field-detection adversarial, fillFields safety, Luhn |
| content/detect.test.ts | 15 | Login/signup/TOTP/card detection, PCH-01-025 CSS.escape |
| content/policy.test.ts | 11 | Password-policy extraction |
| content/passkey-toast/passkey-toast.test.ts | 10 | Passkey toast rendering |
| content/fill.test.ts | 9 | fillFields, cross-origin refusal, card fill |
| content/passkeys-firefox-relay.test.ts | 6 | Relay allowlist, cross-frame block |
| content/{identity-helper,interaction}.test.ts | 8 | Identity sort/filter, interaction targets |
| content/inject.keyguard.test.ts | 2 | PCH-01-007 isTrusted gating — the ONLY test touching inject.ts |
| popup/codex/codex.adversarial.test.ts | 47 | parseRawScan/ChunkBuffer/reconstruct adversarial, PCH-01-010/011/012/013/026 |
| popup/codex/codex.test.ts | 26 | Compression, erasure, verify code, handlers |
| popup/codex/codex.suite.test.ts | 21 | Chunk/stripe planning, Aztec import, bundle hygiene |
| popup/codex/{generate,restore,pdf-import,pdf-import.clamp,browser-bundle}.test.ts | 59 | Erasure pipeline, restore, PDF import, PCH-01-014 clamp, MV-5 |
| popup/views/contracts.test.tsx | 21 | Lock/Export/Health/Entry/Import/Recovery/Vault/Settings contracts |
| popup/views/NoteEditor.toHtml.test.ts | 21 | Note HTML sanitization (THREAT-006) |
| popup/views/{vault-search,Export.csv,zxcvbn-strength,CodexRestoreView,CodexGenerateView,Health}.test | 13 | Search/sort, CSV injection, LV-8 zxcvbn, PCH-01-015 |
| popup/components/GeneratorDrawer.test.ts | 24 | Generator policy constraints (≤128) |
| popup/components/ChirpPairingFlow.test.tsx | 9 | Pairing phases |
| popup/{chirp-pair,chirp-pair-capacitor}.test.ts | 14 | PCH-01-024 gateOnSAS |
| shared/import.test.ts | 17 | Bitwarden/ProtonPass/1Password/CredExchange/Peach, MV-5, PCH-01-019/026 |
| shared/browser.test.ts | 17 | Chrome/Firefox detection + derivatives |
| shared/shortcuts.test.ts | 15 | Keybinding match/format/conflict |
| shared/sync-identity.test.ts | 13 | X25519/vault-id derivation determinism |
| shared/{noise-xx,noise-js,noise-blake2s-kat}.test.ts | 15 | Full XX/NK handshakes, PCH-01-002/003/003b KATs |
| shared/{chirp-handshake,chirp-pairing,chirp-payload,chirp-sync-transport}.test.ts | 35 | SAS, pairing, payload codec, subnet cooldown |
| shared/{device-manifest,generate,backup,ggwave,passkey-utils,domain.homograph,favicon}.test.ts | 53 | Manifest sign/verify, generator, backup, ggwave, rpId (SEV-1-A), homograph (LV-2) |
End-to-end tests (tests/, Playwright, real Chromium + built extension) — 570 declared
| Test File | Tests | Covers |
|---|---|---|
| vault-lifecycle.spec.ts | 55 | Create→unlock→lock→persist, SW restart |
| keyboard-comprehensive.spec.ts | 44 | Global shortcuts, navigation |
| settings-persistence.spec.ts | 42 | Settings survive reload (auto-lock, clipboard, theme) |
| lock-screen.spec.ts | 37 | Master password, recovery words, lock/unlock |
| entry-comprehensive.spec.ts | 35 | Field validation, notes, URLs, edit/delete |
| visual-compliance.spec.ts | 36 | Animation/transition/visual-order compliance |
| password-generator.spec.ts | 33 | Generator UI, entropy, integration |
| autofill-deep.spec.ts | 32 | Icon injection on synthetic forms, matching, security |
| vault-security.spec.ts | 32 | XSS/CSP/session (mix of behavioral + static grep) |
| import-export-robust.spec.ts | 29 | CSV/JSON import/export, duplicates, unicode |
| keyboard-edge.spec.ts | 29 | Focus traps, overlay behavior |
| clipboard.spec.ts | 23 | Copy + timed clear |
| search.spec.ts | 23 | Filtering, empty state, special chars |
| a11y-totp-vault.spec.ts | 22 | ARIA labels, focus order |
| totp-edge.spec.ts | 22 | Base32 parsing, invalid inputs |
| security-visual.spec.ts | 21 | No exposed credentials, safe animations |
| entry-flows-state.spec.ts | 18 | Add/edit/delete/copy/reveal/TOTP flows |
| settings.spec.ts | 10 | Settings visibility, toggles |
| {entry-crud,copy-totp}.spec.ts | 14 | CRUD, TOTP copy |
| codex-pdf-restore.spec.ts | 4 | Codex PDF import (hard-coded local fixture path) |
| autofill.spec.ts | 6 | Content-script injection smoke |
| passkeys.spec.ts | 1 | One real Chrome create()+get() ceremony |
| passkeys-firefox.spec.ts | 0 | 10 skipped (gated on PEACH_E2E_FIREFOX_PASSKEYS=1) |
| {visual-check,diagnostic}.spec.ts | 2 | Placeholders |
Section 3 — Coverage Assessment by Module
src/background/vault.ts
Rating: Adequate Tested: unlock/lock, entry CRUD with storage-failure rollback, changePassword + session re-escrow (PCH-01-008), biometric unlock root-key rehydration (PCH-01-009), constant-time parallel-slot unlock, duress equal-length slots, monotonic timestamps, NFC normalize + legacy heal (LV-1), proto-pollution filter (LV-7), HV-1 clamp + alerts, snapshot bucketing (PCH-01-022), MV-6 import whitelist with passkey/canary shape + HTTPS canary check. Not tested:
purgeExpiredTrashretention cutoff — off-by-one would leak past-retention entries or erase early. No functional test.consolidatePasskeyEntriesmerge heuristic — called after passkey registration; only exercised via the legacy-migration import path, never directly. Wrong winner = duplicate or wrong-credential entry.getIdentitiesdedup logic for autofill identity suggestions — null-timestamp / domain-extraction-failure paths untested.checkBreachHIBP k-anonymity (SHA-1 prefix/suffix) — network stubbed; encoding/parse never verified.restoreEntry/purgeEntry(undelete / hard-delete) — no direct test; restore-after-restore monotonic case untested.consumeLastUnlockFailureReasoncorruption signal (good tag / bad schema, M7) — never asserted; corruption vs wrong-password indistinguishable in test.- Concurrent writes (two
updateEntryracing on the single storage key). Risk: Medium. The crown-jewel module's high-value mutating paths (changePassword, MV-6) are tested, but several destructive/lifecycle paths (trash purge, hard delete, passkey consolidation) and the corruption-vs-wrong-password discrimination are not.
src/background/messages.ts
Rating: Strong
Tested: all 51 message types, MV-1 (origin re-validation + explicitSummon + mismatch→not_found + unparseable→origin_unverified), MV-2 (includeAll behind userGesture), LV-3 (canary rate-limit), LV-4 (recovery-words re-auth), PCH-01-006/007 (EDIT origin), PCH-01-018 (4 destructive ops × content/extension matrix), PCH-01-019 (TOTP code generated, seed stripped), pro gate.
Not tested: GET_ENTRY_FOR_EDIT via sender.url (only sender.tab exercised); AUTOFILL_QUERY includeAll against a locked vault; PCH-01-016 batch-add canary/tag sanitization (BATCH_ADD tested but not the sanitize path); forged rpId+origin match attempt on GET_PASSKEY_FOR_DOMAIN; IPv6 webhook-URL variants ([::1], fc00::/7).
Risk: Low–Medium. PCH-01-016 batch sanitization is the notable security gap (see §4).
src/background/passkey.ts / passkey-crypto.ts
Rating: Strong (Chrome) / Adequate (Ed25519, crypto edges)
Tested: Chrome isUvpaa-when-locked, locked-vault wait, origin resolution (subdomain/exact/fallback), create/get ceremonies, cancel cleanup; Firefox cross-origin rejection (SEV-1-A), forged clientDataJSON.origin rejection, enumeration protection.
Not tested: Ed25519 (-8) keypair round-trip (only -7); algorithm-preference order; p1363ToDer edge cases (r/s=0, high-bit boundaries); malformed privateKeyJwk import; rpId case-insensitivity match; consolidatePasskeyEntries ran-assertion after create.
Risk: Medium — Ed25519 passkeys are a supported algorithm with zero round-trip coverage.
src/background/clipboard.ts / offscreen.ts / alarms.ts
Rating: Strong / Adequate / Adequate
Tested: HV-2 alarm arm + offscreen route + Firefox fallback, PCH-01-021 cold-start hash skip, re-arm on second copy, failed-clear re-arm; auto-lock alarm + idle lock.
Not tested: offscreen createDocument non-"already-exists" error; alreadyWritten=true path; both clipboard APIs failing; clearAutoLock; idle MIN_IDLE_SECONDS clamp boundary; offscreen message-type validation; navigator.clipboard reject → execCommand fallback.
Risk: Low.
src/background/{license,ml,sync-alerts,commands,self-test}.ts
Rating: license Weak, ml Missing, sync-alerts Weak, commands Weak, self-test Strong license.ts: Ed25519 signature verification, base58 decode, prefix/checksum/length — no direct tests (only indirect pro-gate triggering). A broken verifier would not be caught. ml.ts: logistic-regression prediction — no test file; softmax stability, feature-vector completeness untested. sync-alerts.ts: queue filter/bound — no unit tests; tested only via vault. commands.ts: ping timeout, restricted-URL handling — no tests. self-test.ts: PCH-01-020 real-KAT divergence — Strong. Risk: Medium for license (a defeated signature check = free Pro / forged license with no test).
Sync cluster (chirp-, noise-, sync-identity, device-manifest, paired-devices)
Rating: Strong across the board.
Tested: full Noise XX and NK end-to-end handshakes (not just KATs), BLAKE2s cross-repo KAT byte-identical to Android (PCH-01-002), NK handshake-hash binding (PCH-01-003/003b), deviceId local+migration (PCH-01-004), per-entry HKDF separator (PCH-01-005), MV-3 bounds, MV-7 queue + sync-trigger filter, MV-8d key-rotation alert, SAS gate (PCH-01-024), PCH-01-018 pre-msg3 abort (in isolation).
Not tested: subnet-probe→Noise-XX integration (pin check tested in isolation, not against a live wrong-peer responder — the PCH-01-018 threat surface end-to-end); WebSocket onerror/onclose handling (PCH-01-026 #9); timestamp-clamp and manifest-freshness boundary values; rotateSyncIdentity function itself (MV-8b — only the key-rotation alert is tested); chirp playback failure / mic discontinuity.
Risk: Low–Medium. The one notable security gap is the absence of an end-to-end test that a probed wrong-peer never receives the long-term static (PCH-01-018).
src/crypto/peachPayload.ts
Rating: Adequate
Tested: entry/passkey/card/note round-trips, AEAD tamper, bucket padding, PCH-01-001 NFC (NFD-seal→NFC-open, NFC→NFC, codex-restore NFD, legacy raw fallback, CJK+combining), v3 email field.
Not tested: malicious declared-length > cap rejection (MV-4 declared-length path is implicit, never asserted in payload context); malformed/truncated zstd frame; unknown-section skip; section exactly at 64 MiB boundary.
Risk: Medium — the declared-length bomb guard in peachPayload is not directly exercised (only the zstd-output cap is, in decompression-bomb.test.ts).
src/crypto/totp.ts
Rating: Weak
Tested: param threading (SHA256/8/60), legacy defaults, parseOtpauthURI.
Not tested: no RFC 6238 known-answer vectors for SHA1/SHA256/SHA512; time-step boundary roll; clock-skew window; digits/period range validation; base32 secret validation. Production correctness rests entirely on the otpauth library with no independent vector check.
Risk: High for a TOTP feature — a library regression or wrong byte-order would ship silently. A wrong 2FA code is a hard user-facing failure.
src/crypto/{argon2,zstd,peachScript}.ts
Rating: argon2 Adequate, zstd Strong (bounds) / Weak (format), peachScript Strong Not tested: Argon2id KAT vectors + parameter (m/t/p) verification — a silent param downgrade (e.g. memory cost) would not be caught; zstd malformed-frame handling; peachScript multi-MB payloads / Unicode-space normalization. Risk: Medium for argon2 — no test asserts the KDF actually runs at the intended cost.
src/shared/recovery-words.ts + recovery-word-list.ts
Rating: Adequate / Unaudited
Tested: 12-word round-trip, validation (case/whitespace), tamper at every position, recovery cycles, survives changePassword, 100-word BIP39 sampling.
Not tested: BIP39 checksum rejection with a known-good-words/bad-checksum phrase (current "wrong words" test uses Array(12).fill('abandon') which is valid words but its checksum status is unasserted); 128-bit prefix-KEK construction (PCH-01-005 recovery escrow) — no test that bytes 16–31 are zero-padded / that two roots differing only past byte 16 yield the same words; word-list integrity — no test that the 2048-word list is unique and byte-identical to the official BIP39 English list. A single transcription error would silently break recovery for affected phrases with no test catching it.
Risk: High — recovery is the disaster path; the word-list integrity and checksum-rejection gaps are exactly where a silent, unrecoverable data-loss bug hides.
src/shared/import.ts + backup.ts
Rating: Strong / Adequate
Tested: Bitwarden/ProtonPass/1Password-8/CredentialExchange/Peach formats, format detection, LastPass grouping, MV-5 size cap, PCH-01-019 TOTP params, PCH-01-026 single-parse + trim fix; backup wrong-password/malformed/missing-field rejection.
Not tested: CSV formula injection on the import side (Export side is covered by Export.csv.test.ts; import accepts =cmd/@/+ verbatim); truncated CSV; the LV-1 NFC raw-fallback in import.ts/backup.ts — the password !== password.normalize("NFC") branch is never exercised with a genuinely non-NFC password, so the "old non-NFC backup still opens" guarantee is unverified at this layer; 1Password trashed-item skip; CredentialExchange invalid-PKCS8 path.
Risk: Medium — the backup NFC raw-fallback is the canonical pattern PCH-01-001 was modeled on, yet its own decrypt path is untested (see §4).
src/shared/generate.ts
Rating: Strong
Tested: exact length, forbidden-char exclusion (all pools), fallback pool, smart-policy min/max clamp, rejection-sampling no-modulo-bias.
Not tested: all-chars-forbidden → empty pool (potential infinite loop / crash); 128-char boundary; minLength > maxLength policy; NaN policy values.
Risk: Low–Medium — the empty-pool hang is a real (if narrow) availability bug.
src/shared/domain.ts / passkey-utils.ts / browser.ts
Rating: Strong. Tested: homograph detection (Cyrillic/punycode/bare/ASCII/malformed, LV-2); rpId exact/suffix/spoof/bare-eTLD/dotless/case (SEV-1-A); Chrome/Firefox detection + derivatives. Not tested: LV-2 UI surfacing of the warning (only the detector); extractDomain on IPs / multi-part TLDs; rpId with port / IPv6. Risk: Low.
Content / autofill layer
See Section 5 — assessed behavior-by-behavior.
Popup views (App, Settings, Entry, Lock, Import, Add, Vault, Export, Onboarding, Panic, Notes, Trash, ChirpSync)
Rating: Weak (unit) — covered mainly by Playwright e2e.
contracts.test.tsx (21 tests) is the only component-level coverage and verifies: Lock unlock/create + strength gate; Export surfaces; Settings change-password/recovery/license/feedback-XSS/Firefox-consent; Entry copy+canary/TOTP/inline-edit-blur/empty-password-reject; Vault search/sort; Import file-select/dup/replace/onboarding/encrypted-reparse; RecoveryWordEntry → RECOVER_WITH_WORDS. Add, Onboarding, Panic, TrashView, ChirpSync, NotesEditor, RecoveryWordDisplay have zero component tests. The 2,146-line Settings and 1,779-line Entry views have only the handful of contract assertions above.
Risk: Medium — large stateful views rely entirely on e2e, which (see §5/§8) is happy-path-heavy.
Codex (popup/codex/*)
Rating: Strong.
Tested: erasure encode/decode with loss, ChunkBuffer state machine, parseRawScan/parseChunkEnvelope adversarial, reconstruct fingerprint checks, full single-stripe round-trip, all of PCH-01-010/011/012/013/014/015/026 + MV-5.
Not tested: multi-stripe restore (generate tests multi-stripe; restore only single-stripe); MV-9 pdf.js isEvalSupported:false/disableFontFace/Range/Stream flags (no test asserts they're set); reed-solomon try/finally free on trap; full UI generate→PDF→restore round-trip.
Risk: Medium — multi-stripe restore (large vaults) and the MV-9 sandbox flags are real gaps.
Section 4 — High-Risk Untested Paths
Untested: BIP39 word-list integrity + checksum rejection
File: src/shared/recovery-word-list.ts, recovery-words.ts:110-113 Risk: Recovery is the disaster-recovery path. No test verifies the 2048-word list is unique and byte-identical to the canonical BIP39 English list, and no test rejects a valid-words/invalid-checksum phrase. A single transcription error or a checksum-bypass would make some phrases silently unrecoverable — discovered only by a locked-out user. Test: (a) assert list length 2048, all unique, all 3–8 ASCII letters, SHA-256 of the joined list == known BIP39 digest; (b) take a valid 12-word phrase, swap the last word for another valid word, assert recovery fails on checksum. Priority: High
Untested: 128-bit prefix-KEK recovery-escrow construction (PCH-01-005)
File: src/shared/recovery-words.ts:118-154 Risk: Recovery words encode only the first 16 bytes of the 32-byte root key (bytes 16–31 zero-padded). If that invariant breaks, either recovery silently fails or the security claim (128-bit) is wrong. No test asserts the prefix/zero-pad behavior or the HKDF salt/info usage. Test: two root keys identical in bytes 0–15 but differing in 16–31 produce identical recovery words; HKDF output changes when salt changes. Priority: High
Untested: TOTP against RFC 6238 vectors
File: src/crypto/totp.ts
Risk: No independent verification of generated codes for SHA1/SHA256/SHA512. A library regression ships a wrong 2FA code with no test failing — a hard, visible breakage for every 2FA login.
Test: RFC 6238 Appendix B vectors (secret 12345678901234567890, known timestamps) for all three algorithms; verify period-boundary roll.
Priority: High
Untested: peachPayload declared-length decompression bomb (MV-4)
File: src/crypto/peachPayload.ts (pre-decompressZstd declared-length check)
Risk: Only the zstd output cap is tested (decompression-bomb.test.ts). The declared-length-before-inflate guard in the payload parser — the first line of MV-4 defense on the Codex/import path — has no regression test and could be removed unnoticed.
Test: payload claiming length > MAX_DECLARED_LENGTH throws before any decompression; sum-of-sections > MAX_DECOMPRESSED_BYTES rejected.
Priority: High
Untested: backup/import NFC raw-fallback decrypt (LV-1 / PCH-01-001 sibling)
File: src/shared/backup.ts:96-111, src/shared/import.ts:96-111
Risk: This is the canonical normalize-with-raw-retry pattern PCH-01-001 cites. The raw:true fallback that opens pre-LV-1 non-NFC backups is never exercised with a genuinely non-NFC password. If the fallback is broken, users with non-ASCII passwords are permanently locked out of old backups.
Test: seal a backup with raw (un-normalized) non-NFC password bytes → import → succeeds via raw fallback; NFC password still opens normally.
Priority: High
Untested: Ed25519 passkey round-trip
File: src/background/passkey-crypto.ts:164-238 Risk: Ed25519 (-8) credentials are supported but only ES256 (-7) is tested. A broken PKCS8 import/export or COSE encoding for Ed25519 would let registration succeed and authentication silently fail. Test: generate -8 keypair → sign assertion → verify; export/re-import private key. Priority: Medium
Untested: license signature verification
File: src/background/license.ts:94-99 Risk: Ed25519 license verification has no direct test. A defeated/short-circuited check = forged Pro licenses, with nothing failing. Test: valid signature accepts; tampered payload / wrong signature / wrong prefix / bad length all reject. Priority: Medium
Untested: PCH-01-018 subnet-probe identity leak (end-to-end)
File: src/shared/chirp-sync-transport.ts:132-140 (verifyRemoteStatic) + noise-xx
Risk: The pin check is tested in isolation, but no integration test exercises a subnet scan finding a wrong-pubkey host and confirms the local long-term static is not sent before msg3. This is exactly the threat PCH-01-018 fixed.
Test: simulate openChirpSyncSession against a responder whose static ≠ pinned → assert abort before msg3, no static transmitted.
Priority: Medium
Untested: PCH-01-016 batch-add canary/tag sanitization (MV-6 on import/batch)
File: src/background/messages.ts (BATCH_ADD_ENTRIES → sanitizeEntryDraft)
Risk: MV-6 whitelist is tested on the restore path (vault.test.ts) but not on BATCH_ADD_ENTRIES/import, which is the actual attacker-reachable bulk surface. A non-HTTPS canary webhook or unbounded tags could slip in via import.
Test: batch-add with a non-HTTPS canary webhook + oversized tag array → assert sanitized/rejected.
Priority: Medium
Untested: MV-9 pdf.js sandbox flags
File: src/popup/codex/pdf-import.ts:259
Risk: No test asserts getDocument is called with isEvalSupported:false/disableFontFace/Range/Stream. A refactor could silently re-enable eval in the PDF parser (untrusted-PDF attack surface).
Test: spy on getDocument, assert the hardened options object.
Priority: Medium
Section 5 — The Autofill Injection Layer
Headline: The autofill logic helpers (detect, fill, policy, interaction, identity-helper) are well unit-tested (≈90 tests). The two largest and most behaviorally complex files — inject.ts (4,405 lines) and index.ts (1,263 lines) — are almost entirely untested at the unit level. inject.ts has exactly 2 tests (inject.keyguard.test.ts, PCH-01-007 isTrusted). index.ts has no dedicated test file at all. The e2e autofill-deep.spec.ts (32 tests) exercises icon injection but against page.setContent() synthetic HTML, not real pages, and does not drive the orchestration state machine in index.ts.
Behavior-by-behavior:
| Behavior (source) | Coverage | Notes |
|---|---|---|
| Field detection — login/signup/email-first/TOTP (detect.ts) | Strong (unit) | detect.test.ts + content.adversarial.test.ts (hidden/disabled/honeypot/0x0/types) |
| Card form detection + Luhn + network (detect/fill) | Strong (unit) | content.adversarial + detect.test.ts (Stripe iframe, autocomplete, name/id heuristics) |
| Domain matching (extractDomain) | Strong (unit) | content.adversarial — github.com.evil.com→evil.com, www/sub/case/co.uk, ~30 cases |
fillFields cross-origin-safe + React setNativeValue |
Strong (unit) | fill.test.ts — refuses cross-origin form action, dispatches input/change |
| CSS.escape on field ids (PCH-01-025) | Strong (unit) | detect.test.ts — special chars + quotes don't throw |
| isTrusted keydown gating (PCH-01-007) | Strong (unit) | inject.keyguard.test.ts — untrusted Arrow/Enter/printable no-op |
resolveAutofillInteractionTargets shouldOpen logic |
Adequate (unit) | interaction.test.ts — 4 cases (passive gate, detected fields, explicit email/password click) |
| Password-policy extraction (policy.ts) | Strong (unit) | policy.test.ts — maxlength/pattern/nearby-text/forbidden-chars/no-leak |
| Firefox passkey relay allowlist | Strong (unit) | passkeys-firefox-relay.test.ts — allowed/blocked types, cross-frame block, null payload |
handleFieldInteraction orchestration (index.ts) |
None (unit) | The central state machine — ML-predict timeout race, field-type resolution, locked-vault prompt, signup-mode branch — untested except indirectly by e2e |
| 3-layer dismissal model (index.ts: lastDismissedField, DISMISS_LOCK_MS, fill-success lock) | None | Complex, regression-prone (the comments cite v1.4.14/§4.7/§4.12 prior regressions). Zero direct tests |
wasAlreadyFilled / recordFillSuccess form+page-grace logic |
None | The "don't re-pop after fill" guarantee has no unit test |
initialScan aggression levels (0/1/2) + manualSummonDomains recall |
None | The aggressive password-field injection on recall domains is untested |
| MutationObserver re-attach + 200ms debounce | None | The "do not lower" debounce regression guard has no test |
| SUMMON_AUTOFILL routing (shortcut / passkey-unlock / search-palette) | None | Message-handler branches untested |
| VAULT_UNLOCKED re-trigger vs passkeySummonActive | None | The "no double-trigger race" claim is asserted only by comment |
| showAutofillSelector / showTOTPSelector / showCardSelector rendering + keyboard nav (inject.ts) | None | 4,405 lines of inline-UI rendering, the lazy MV-2 search load, the smart-generator drawer — untested beyond the 2 keyguard tests |
| showRememberSiteToast, passkey toasts/offer/available (inject.ts) | None | Untested |
| TOTP digit-box fill + email-first flow + signup confirm-password fill | None (unit) | Logic lives in index.ts/inject.ts; not unit-covered |
| MV-1/MV-2 enforcement at the content boundary | Partial | Enforced & tested in messages.ts (background side). The content-side caller (explicitSummon flags, lazy loadSearchableEntries thunk) is untested |
E2E reality (autofill-deep.spec.ts): injects icons onto synthetic forms and checks locators/matching/security guards. It does not: submit forms and verify post-submit suppression; test email-first/two-step (Google/Microsoft) flows; test the inline chip dismissal model; test cross-origin iframe fill (the iframe test is synthetic and unverified); test against any real login page. As a result the content-script orchestration is effectively uncovered — neither unit nor realistic-integration.
Conclusion: This is the highest-complexity, lowest-coverage area in the codebase. The pure functions are safe; the stateful glue that decides when to open/suppress/fill — historically the source of multiple regressions — is the single largest test gap.
Section 6 — PCH / Audit Fix Coverage
Source reports (peach-browser-security-audit.md, peach-browser-technical-report.md) were draft-only and are no longer on disk; findings reconstructed from LOG.md and the two fix briefs in ~/Downloads.
First-round audit (Fable, v3.7.x)
| Finding | Fix | Regression test? | Quality |
|---|---|---|---|
| HV-1 | clampSyncTimestamp + sync alerts | Yes | Strong — vault.sync-clamp.test.ts clamp + REMOTE_PASSWORD_CHANGE + REMOTE_DELETE |
| HV-2 | clipboard via chrome.alarms + offscreen | Yes | Strong — clipboard.test.ts arm/route/fallback |
| MV-1 | GET_ENTRY_FOR_FILL origin re-validation | Yes | Strong — messages.test.ts 4 cases (match/mismatch/summon/unparseable) |
| MV-2 | includeAll behind userGesture | Yes | Strong — messages.test.ts strip-without-gesture + full-with-gesture |
| MV-3 | sync frame/count/size bounds | Yes | Strong — chirp-sync-vault-adapter.test.ts all bounds |
| MV-4 | decompression bomb caps | Partial | zstd output cap tested; declared-length cap in peachPayload NOT tested |
| MV-5 | import file-size limits | Yes | Strong — import/backup/pdf-import tests |
| MV-6 | import whitelist (sanitizeRestoredEntry) | Partial | Restore path tested; batch-add/import path (PCH-01-016) NOT directly tested |
| MV-7 | vouched-device confirmation | Yes | Strong — adapter queue + sync-trigger filter |
| MV-8 | rotateSyncIdentity + key-rotation notify | Partial | DEVICE_KEY_ROTATED tested; rotateSyncIdentity itself NOT directly tested |
| MV-9 | pdf.js isEvalSupported:false | No | No test asserts the hardened getDocument options |
| LV-1 | NFC normalize master password | Yes | Strong — argon2.test.ts parity + vault.test.ts heal |
| LV-2 | homograph warning | Partial | detectHomograph tested; UI surfacing untested |
| LV-3 | canary rate-limit | Yes | Strong — messages.test.ts 3 cases |
| LV-4 | GET_RECOVERY_WORDS re-auth | Yes | Strong — messages.test.ts 3 cases |
| LV-5 | dedup onConnect + nonce port | Yes | Adequate — entry.lock-port.test.ts 2 cases |
| LV-6 | lockfile standardization | n/a | Build config — no test (acceptable) |
| LV-7 | ggwave new Function + proto-pollution | Partial | Proto-pollution tested (vault.test.ts); ggwave new Function removal verified only by build artifact, no test |
| LV-8 | zxcvbn → @zxcvbn-ts/core | Yes | Strong — zxcvbn-strength.test.ts |
| LV-9 | exact-pin crypto deps | n/a | package.json — no test (acceptable) |
Second-round technical report (Fable, v3.8.1 → PCH-01-001..026)
| Finding | Regression test? | Quality |
|---|---|---|
| PCH-01-001 NFC export/restore + raw fallback | Yes | Strong — 5 cases in peachPayload.test.ts. (Gap: backup.ts/import.ts sibling raw-fallback untested — §4) |
| PCH-01-002 Noise BLAKE2s | Yes | Strong — noise-blake2s-kat.test.ts cross-repo byte-identical |
| PCH-01-003 / 003b NK handshakeHash binding | Yes | Strong — noise-js.test.ts binds + rejects-on-modify |
| PCH-01-004 deviceId local + migration | Yes | Strong — chirp-identity.test.ts |
| PCH-01-005 HKDF null separator | Yes | Strong — adapter round-trip + legacy-fails-closed |
| PCH-01-006/007 GET_ENTRY_FOR_EDIT origin | Yes | Strong — messages.adversarial.test.ts 4 cases |
| PCH-01-007 inject isTrusted keydown | Yes | Adequate — inject.keyguard.test.ts 2 cases |
| PCH-01-008 re-escrow after changePassword | Yes | Adequate — vault.session-escrow.test.ts |
| PCH-01-009 biometric rootKey rehydrate | Yes | Adequate — vault.test.ts |
| PCH-01-010 deriveShardCounts cap | Yes | Strong — codex.adversarial.test.ts |
| PCH-01-011 stripe-count cap | Yes | Strong — codex.adversarial.test.ts |
| PCH-01-012 isInteger on i/t/s/S | Yes | Strong — codex.adversarial + pdf-import.test.ts |
| PCH-01-013 fingerprint length | Yes | Strong — codex.adversarial.test.ts |
| PCH-01-014 PDF page/dimension clamp | Yes | Strong — pdf-import.clamp.test.ts |
| PCH-01-015 untrimmed passphrase | Yes | Strong — CodexRestoreView.test.tsx (untrimmed + all-whitespace) |
| PCH-01-016 sanitizeCanary/Tags in batch | No | No direct batch-sanitization test (MV-6 batch surface) |
| PCH-01-017 handleRestoreFromCodex validation | Yes | Strong — codex-handlers.test.ts (malformed code, NaN ts, oversize) |
| PCH-01-018 destructive-op gating | Yes | Strong — messages.adversarial.test.ts 4 ops × matrix |
| PCH-01-018 subnet-probe identity defer | Partial | Pin tested in isolation; end-to-end probe→wrong-peer untested |
| PCH-01-019 TOTP otpauth params | Partial | Param threading tested; no RFC vectors, -8/SHA512 path |
| PCH-01-020 self-test KAT | Yes | Strong — self-test.test.ts divergence |
| PCH-01-021 clipboard cold-start hash | Yes | Adequate — clipboard.test.ts |
| PCH-01-022/023 history entryCount bucket | Yes | Strong — vault-history.test.ts |
| PCH-01-024 gateOnSAS | Yes | Adequate — chirp-pair.test.ts |
| PCH-01-025 CSS.escape detect.ts | Yes | Strong — detect.test.ts |
| PCH-01-026 (10 items) | Partial | ChunkBuffer never-throws ✓, out-of-range≠duplicate ✓, CSV single-parse ✓, SAVE_NOTE typed (implicit). Untested: save.ts dead-observer removal, reed-solomon try/finally, generate.ts step renumber, pdf-import progress total, chirp-sync-transport WS-error handling |
Fixes with NO or only weak regression test (revert-risk): MV-9 (none), PCH-01-016 (none), LV-7 ggwave (build-only), MV-4 declared-length (none), MV-8 rotateSyncIdentity (none), LV-2 UI surfacing (none), PCH-01-018 probe-integration (isolation only), PCH-01-019 RFC vectors (none), several PCH-01-026 sub-items.
Section 7 — Error Path Coverage
| Error path | Tested? | How well |
|---|---|---|
| Import malformed/truncated data | Partial | backup.ts rejects malformed JSON; truncated CSV / mid-parse corruption untested; import.ts returns "unknown" on bad format but graceful-failure not asserted |
| Import wrong backup password | Yes | backup.test.ts + import.test.ts reject wrong password (note: masks non-tag crypto errors as "INVALID_EXPORT_PASSWORD") |
| Vault unlock wrong password | Yes | vault.test.ts + lock-screen.spec.ts; but corruption (good tag/bad schema) vs wrong-password not distinguished in test |
| Network failure in feedback/canary | Partial | Canary rate-limit tested; actual fetch failure / HIBP network error not e2e tested (stubbed) |
| Service-worker eviction mid-operation | Weak | vault-lifecycle.spec.ts covers SW-restart→lock-screen; eviction during create/import/clipboard-timer/autofill untested |
| Sync peer disconnect mid-transfer | Weak | WebSocket onerror/onclose code present but not regression-tested (PCH-01-026 #9) |
| Codex scan with damaged/partial code | Strong | codex.adversarial.test.ts — tampered/missing/parity-only/excess-loss, fingerprint mismatch |
| Autofill on page with no password fields | Partial | index.ts has the path (search-palette / email-first); not unit-tested; e2e covers some no-form cases |
| Entry with all optional fields empty | Partial | peachPayload.test.ts "handles missing optional fields" round-trips; backup.ts empty-password contradiction (defaults "" then validator rejects) untested |
Pattern: Error inputs (malformed crypto/codex/sync payloads) are well covered by the adversarial suites. Error conditions (eviction, disconnect, network failure, partial writes) are weakly covered because they require infrastructure (SW termination, two-device sync, network mocking) the suite doesn't have.
Section 8 — Integration vs Unit Balance
- Unit: ~1,062 tests, the bulk of the suite. Crypto, sync, message-router, codex, detect/fill — all strongly unit-tested. This is where the suite is strongest.
- E2E (Playwright): ~570 tests against the real built extension in Chromium. Covers vault lifecycle, entry CRUD, settings persistence, keyboard, lock screen, clipboard, generator, search well. This is genuine integration for the popup.
- Behavioral/contract:
contracts.test.tsx(21) is the only component-contract layer; thin relative to the ~12k LOC of popup views.
Assessment: For a password manager, the interesting bugs are in component interactions, and the suite has two systemic integration gaps:
- The content-script ↔ page boundary is not integration-tested against real pages.
autofill-deep.spec.tsuses syntheticsetContentHTML and never drivesindex.ts's state machine or a real form submit. The single highest-value integration test — create vault → visit a real login page → autofill → submit → verify suppression — does not exist. - Cross-device sync (chirp pairing, Noise handshake, manifest exchange) is exhaustively unit-tested but has zero end-to-end coverage (it needs two devices + mDNS). The Noise handshakes are tested initiator↔responder in-memory, which is the best achievable without hardware, but the full pairing→sync→merge flow across two real instances is unverified.
The suite is not "mostly unit tests" in volume terms (e2e is a third of it), but the e2e coverage is concentrated on the popup and thin exactly where integration matters most (autofill on real pages, sync across devices). It skews happy-path: lots of "does X work" and few "does X fail gracefully / does X suppress correctly after Y."
Section 9 — Prioritized Gap List
Priority 1 — Must Have Before Audit
T-001: BIP39 word-list integrity check File: src/shared/recovery-word-list.ts — assert length 2048, all unique, all 3–8 ASCII letters, SHA-256(joined) == canonical BIP39 English digest. Why P1: silent transcription error = unrecoverable vaults; no test guards it.
T-002: BIP39 checksum rejection File: src/shared/recovery-words.ts:110-113 — valid 12 words with a deliberately wrong checksum (swap last word) must fail recovery. Why P1: PCH-01-005 escrow integrity; current "wrong words" test uses all-valid words with unasserted checksum.
T-003: 128-bit prefix-KEK invariant (PCH-01-005) File: src/shared/recovery-words.ts:118-154 — two root keys equal in bytes 0–15, differing in 16–31, yield identical recovery words; bytes 16–31 zero-padded on reconstruct. Why P1: silent escrow regression / wrong security claim.
T-004: TOTP RFC 6238 known-answer vectors (PCH-01-019) File: src/crypto/totp.ts — RFC 6238 App. B vectors for SHA1/SHA256/SHA512 at fixed timestamps; period-boundary roll. Why P1: a wrong 2FA code is a hard user failure with no current guard.
T-005: peachPayload declared-length bomb (MV-4) File: src/crypto/peachPayload.ts — payload declaring length > MAX_DECLARED_LENGTH throws before decompress; section-sum > MAX_DECOMPRESSED_BYTES rejected. Why P1: first-line MV-4 defense on import/codex, no regression test.
T-006: backup/import NFC raw-fallback decrypt (LV-1/PCH-01-001 sibling) File: src/shared/backup.ts:96-111, import.ts:96-111 — seal with raw non-NFC password → import succeeds via raw fallback; NFC password still opens. Why P1: canonical pattern, data-loss risk on disaster path, never exercised.
T-007: MV-9 pdf.js sandbox flags File: src/popup/codex/pdf-import.ts:259 — spy on getDocument, assert isEvalSupported:false + disableFontFace/Range/Stream. Why P1: untrusted-PDF eval surface, zero test.
T-008: PCH-01-016 batch-add canary/tag sanitization File: src/background/messages.ts BATCH_ADD_ENTRIES — non-HTTPS canary webhook + oversized tags via batch/import → sanitized or rejected. Why P1: MV-6's actually-attacker-reachable surface, untested.
T-009: license Ed25519 signature verification File: src/background/license.ts:94-99 — valid accepts; tampered payload / wrong sig / wrong prefix / bad length reject. Why P1: defeated check = forged Pro, no test.
T-010: PCH-01-018 subnet-probe identity-leak integration File: src/shared/chirp-sync-transport.ts:132-140 — probe finds wrong-pubkey host → abort before msg3, no local static transmitted. Why P1: the exact threat PCH-01-018 fixed, only isolation-tested.
Priority 2 — High Value
T-011: index.ts dismissal model — unit-test the 3 layers (lastDismissedField per-field lock; DISMISS_LOCK_MS global lock; fill-success wasAlreadyFilled form+page-grace). Drive focusin/Escape/click-outside sequences; assert no reopen after fill, no reopen on rapid cross-field focus. (Refactor index.ts to export the predicate functions.) Why P2: historically regression-prone (v1.4.14, §4.12), zero coverage.
T-012: index.ts handleFieldInteraction orchestration — ML-predict 150ms timeout race, locked-vault prompt, signup-mode (no domain entries → identities + confirm-password), email-first (no pwField) path. Why P2: central autofill decision logic, untested.
T-013: index.ts initialScan aggression levels — level 0 (no form → skip), level 1, level 2 final-pass injecting bare password fields; manualSummonDomains recall mapping. Why P2: aggressive injection on recall domains untested.
T-014: SUMMON_AUTOFILL / VAULT_UNLOCKED message routing (index.ts) — shortcut vs passkey-unlock vs search-palette branch; VAULT_UNLOCKED re-trigger vs passkeySummonActive (no double-trigger). Why P2: message-handler branches untested.
T-015: Ed25519 (-8) passkey round-trip — generate → sign → verify → export/re-import. Why P2: supported algorithm, only ES256 tested.
T-016: MV-8 rotateSyncIdentity — rotate generates new root/syncMasterKey, clears paired devices, old key can't decrypt. Why P2: lost/stolen-device revocation, function untested.
T-017: Codex multi-stripe restore — generate a >200-shard (2-stripe) codex, restore, assert vault matches. Why P2: large-vault restore path, only single-stripe tested.
T-018: vault corruption vs wrong-password discrimination — unlock with good AES-GCM tag but bad schema → consumeLastUnlockFailureReason signals corruption (M7), distinct from wrong-password. Why P2: user-facing error correctness.
T-019: Argon2id KAT + parameter assertion — pin a known (password, salt) → expected key with m/t/p; assert cost params can't silently downgrade. Why P2: KDF strength unverified.
T-020: WebSocket sync-disconnect handling (PCH-01-026 #9) — onerror/onclose mid-handshake → session aborts cleanly, parked receive() rejects, no hang. Why P2: peer-disconnect resilience, no test.
T-021: CSV formula-injection on import — import CSV with =cmd/@/+/- prefixed fields → values sanitized or flagged (mirror Export.csv coverage on the import side). Why P2: known injection class, import side uncovered.
T-022: GET_ENTRY_FOR_EDIT via sender.url + locked-vault includeAll — EDIT origin check through sender.url (not just tab); AUTOFILL_QUERY includeAll on a locked vault returns vault_locked before processing. Why P2: untested branches of tested fixes.
T-023: vault.consolidatePasskeyEntries — after passkey create, same-domain TOTP/login entries merge with correct winner (lastUsed ranking). Why P2: called on every registration, untested directly.
T-024: vault.purgeExpiredTrash retention cutoff — entries older than retention purged, within-window kept, boundary exact. Why P2: data-deletion correctness.
Priority 3 — Good To Have
T-025: generate.ts empty-pool guard — all chars forbidden → no infinite loop / defined error; 128-char boundary; minLength>maxLength policy.
T-026: timestamp-clamp + manifest-freshness boundaries — now+SKEW±1ms; issuedAt at exact freshness edge.
T-027: full Codex UI round-trip e2e — generate → download PDF → re-upload → restore → vault matches (replace hard-coded fixture path with a generated one).
T-028: autofill real-page e2e — host a fixture login page (with form submit), assert icon→fill→submit→post-submit suppression; email-first two-step; cross-origin iframe.
T-029: inject.ts selector keyboard nav — Arrow/Enter selection, search box (MV-2 lazy load fires GET on gesture), Escape dismiss — beyond the isTrusted guard.
T-030: ml.ts predictBatch — empty/single/large batches, softmax stability with extreme weights.
T-031: SW eviction mid-operation — terminate SW during import/clipboard-timer, assert recovery.
T-032: backup.ts empty-password contradiction — login entry with no password: define and assert behavior (currently defaults "" then validator rejects).
T-033: Firefox passkey e2e — un-skip passkeys-firefox.spec.ts in CI or document the manual gate.
T-034: LV-2 homograph UI surfacing — non-ASCII URL saved → warning rendered in entry edit.
T-035: HIBP breach-check integration — mock endpoint, verify k-anonymity (5-char SHA-1 prefix sent, suffix matched).
T-036: reed-solomon try/finally free (PCH-01-026 #8) — WASM instance freed on reconstruct trap.
T-037: clipboard double-failure / alreadyWritten path — both clipboard APIs fail; alreadyWritten=true skips write.
T-038: favicon canvas fallbacks — titleInitials/titleBackground determinism, tainted-canvas error handling.
T-039: chirp pairing failure paths — playback failure, mic discontinuity, replay of captured chirp (nonce defeats it).
T-040: sync alert queue bounds — sync-alerts MAX_PENDING boundary, malformed-alert filtering.
Section 10 — Summary
The numbers: 109 test files, ~1,632 declared tests (~3,200 executed after parameterization) against ~44k LOC. By volume this is a well-tested codebase — roughly 1 declared test per 27 lines, with the security-critical primitives (crypto round-trips, Noise handshakes, codex erasure, message-router origin checks, recovery word cycles) genuinely well covered, and 24 of 26 PCH findings plus most audit findings carrying real regression tests.
The biggest coverage gap, in one sentence: the 5,668-line content-script autofill orchestration (index.ts + inject.ts) — the most regression-prone code in the project — has essentially no unit coverage and only synthetic, state-machine-bypassing e2e coverage, so the logic deciding when to open, suppress, and fill is untested at exactly the layer where past regressions happened.
The one test that would buy the most confidence: a real-page autofill e2e (T-028) — load an actual login form, autofill, submit, and assert the popup is suppressed afterward — because it exercises index.ts's entire decision/dismissal/fill-success machine end-to-end in one go, the single largest untested surface.
Runner-up by risk-density: the recovery-path trio (T-001/T-002/T-003) — BIP39 list integrity, checksum rejection, and the 128-bit prefix-KEK invariant — because that is where a silent, unrecoverable data-loss bug would hide with no current test to catch it.
Readiness for a fresh external review: Good on cryptography, sync protocol, codex, and the background message boundary — a reviewer will find these defensible and well-pinned. They will, correctly, flag (1) the autofill content-script layer as under-tested relative to its complexity and trust position, (2) the recovery/BIP39 integrity gaps on the disaster-recovery path, (3) TOTP lacking RFC vectors, and (4) a handful of fixes (MV-9, PCH-01-016, MV-4 declared-length, license verification) that are implemented but have no regression test and could be reverted unnoticed. Closing Priority 1 (T-001–T-010) before review would neutralize the most likely "this critical path has no test" findings; Priority 2 would close the autofill-orchestration gap that a thorough reviewer will otherwise raise.