peach_audit_v1430.md

21.3 KB · raw

rendered · source

Peach Production-Readiness Audit — v1.4.30

Date: 2026-04-22 Scope: Everything except sync (per peach_audit_instructions.docx §1.4) Baseline: v1.4.29 Deliverable version: v1.4.30


14.1 Executive Summary

The audit covered §3 Vault Integrity, §4 Lock/Unlock, §5 Export/Import, §6 Entry CRUD, §7 SolidJS State, §8 Autofill, §9 Breach/Canary, §10 Passkeys, §11 Messages, §12 Code Quality, §13 Data-Loss Vectors. 27 findings: 2 P0 code, 3 P0 test gaps, 13 P1 code, 6 P1 test gaps, 3 P2. §15 Frozen-UI Bug is fixed in this bump (Fixes A/B/C/D applied).

Most critical: FIND-001 — changePassword() can leave the in-memory derivedKey out of sync with the on-disk Slot A ciphertext if any failure occurs in the narrow window between writeSlot(SLOT_A, …) and the key swap. Once in that state, no subsequent persistEntries() call can produce a decryptable vault, and neither the old nor new password will unlock it. Fix first.

Overall posture is strong: crypto primitives, timing-attack resistance, Shadow-DOM isolation, HIBP k-anonymity, canary IP allowlist, and passkey rpId enforcement all check out.


15 Frozen-UI Bug — FIXED

Applied all four fixes from §15 in this bump:

Checklist vs §15.8:


14.2 Findings (by priority)

P0 — Data loss, lockout, or silent corruption

FIND-001 [P0] changePassword() key/disk atomicity gap

FIND-002 [P0 latent] restoreEntries("replace", []) silently wipes the vault

FIND-003 [P0 test gap] changePassword() happy path

FIND-004 [P0 test gap] changePassword() wrong current password

FIND-005 [P0 test gap] changePassword() with duress configured — Slot B byte-unchanged

P1 — User-visible wrong behaviour

FIND-010 [P1] changePassword() takes no snapshot

FIND-011 [P1] Export.tsx passwordsMatch() memo ignores the confirm field

FIND-012 [P1] Content script runtime message listener does not validate sender

FIND-013 [P1] GET_ENTRY_FOR_FILL has no explicit unlock gate

FIND-014 [P1 style] Background sender check short-circuits on sender.id === undefined

FIND-015 [P1] PING and SUMMON_AUTOFILL in MessageType union but no handler

FIND-016 [P1] sanitizeEntryPatch silently clears password on login entries

FIND-017 [P1] deduplicateEntries() is unguarded in decoy mode

FIND-018 [P1] chrome.storage.local quota errors are unhandled

FIND-019 [P1] clearVaultEntries() has no vault-layer confirmation

FIND-020 [P1] No proactive popup notification of background auto-lock

FIND-021 [P1] Snapshot race: void takeSnapshot() may read pre-commit Slot A

FIND-022 [P1 test gap] restoreFromSnapshot() happy path

FIND-023 [P1 test gap] restoreFromSnapshot() wrong password

FIND-024 [P1 test gap] CJK round-trip with 0 entries

P2 — Code quality / minor UX

FIND-040 [P2] "generate" is dead code in View union

FIND-041 [P2] CSV export preserves embedded newlines in notes

FIND-042 [P2] Pre-existing test failure in src/content/index.test.ts


14.3 Clean Bills

Explicit per §14.3:


14.4 Missing Test Cases (confirmed absent)

Add to src/background/vault.test.ts:

test("changePassword happy path — new password unlocks, old rejected", …);
test("changePassword wrong current password → false, disk unchanged", …);
test("changePassword with duress configured — Slot B ciphertext unchanged", …);
test("restoreFromSnapshot with correct password — entries restored", …);
test("restoreFromSnapshot with wrong password — no writes, returns false", …);
test("restoreEntries('replace', []) — refuses to wipe", …); // after FIND-002 fix

Add to src/crypto/cjkRoundTrip.test.ts:

test("round-trip 0 entries — phrase encodes and decodes back to empty vault", …);

Add to src/shared/import.test.ts (or a new backup.test.ts):

test("importEntriesFromPeachJson with wrong password throws INVALID_EXPORT_PASSWORD", …);

14.5 Recommended Patch Order

Ship these in a single v1.4.30 commit series, in this order:

  1. §15 fixes (popup.css + App.tsx) — applied in this bump. Addresses the daily-driver top-priority regression.
  2. FIND-001 — changePassword key/disk atomicity. Tight patch, no API change.
  3. FIND-002 + FIND-019 — vault-layer guards against empty-replace and unconfirmed clear. Defense-in-depth.
  4. FIND-010 — snapshot after changePassword. Adds one line inside FIND-001's patch.
  5. FIND-016 — reject password-clear on login entries.
  6. FIND-017 — guard dedupe in decoy mode.
  7. FIND-013 — explicit unlock gate and passkey filter on GET_ENTRY_FOR_FILL.
  8. FIND-011 — fix passwordsMatch memo.
  9. FIND-012 — content-script sender validation.
  10. FIND-020 — proactive VAULT_LOCKED broadcast.
  11. FIND-018 — quota handling.
  12. FIND-014 + FIND-015 — semantic sender check; MessageType cleanup.
  13. FIND-021 — pass snapshot content explicitly instead of re-reading.
  14. FIND-040 / FIND-041 — P2 cleanups.
  15. Test gaps (FIND-003/004/005/022/023/024) — add alongside the relevant fix commits.

End of audit deliverable. §15 code fixes are live in this bump; all other findings are documented for developer approval before patching.