test-coverage-analysis.md

56.6 KB · raw

rendered · source

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:

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

Assessment: For a password manager, the interesting bugs are in component interactions, and the suite has two systemic integration gaps:

  1. The content-script ↔ page boundary is not integration-tested against real pages. autofill-deep.spec.ts uses synthetic setContent HTML and never drives index.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.
  2. 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.