9bd1e7c2cf37

add PCH-02-004/005/006 fix drafts: exact-pin crypto-adjacent deps with bun.lock versions + new WASM-PROVENANCE.md, legacy PBKDF2 KDF docblock with 250k-iteration correction + Import.tsx re-export notice, vault_id LAN-linkability privacy note

time
2026-06-12 20:12 UTC · 4 months ago
machine
koh2
agent
claude-fable-5
session
pch-02 fix drafts
parent
53ce63c0fe08
4 files src/drafts/PCH-02-FIXES/PCH-02-004-exact-pin-deps.patch.md+84 −0src/drafts/PCH-02-FIXES/PCH-02-005-legacy-pbkdf2-doc.patch.md+118 −0src/drafts/PCH-02-FIXES/PCH-02-006-vault-id-privacy-note.patch.md+48 −0src/drafts/PCH-02-FIXES/WASM-PROVENANCE.md+57 −0
src/drafts/PCH-02-FIXES/PCH-02-004-exact-pin-deps.patch.md+84 −0
@@ -0,0 +1,84 @@1+# PCH-02-004 — Exact-pin crypto-adjacent dependencies2+3+**Severity:** Informational4+**Target files:** `package.json:40-46`, `bun.lock` (regenerated), new5+`WASM-PROVENANCE.md` (see deviation note)6+**Report:** peach-code-review.md, Finding PCH-02-0047+8+## Pinned versions — read from bun.lock, pin-in-place, NO upgrades9+10+Every version below is the exact version already resolved and installed11+(verified against `bun.lock` on 2026-06-12). Pinning changes the range string12+only; the installed tree is unchanged.13+14+| Dependency | Current range | Pin to (from bun.lock) |15+|---|---|---|16+| `otpauth` | `^9.5.0` | `9.5.0` |17+| `pako` | `^2.1.0` | `2.1.0` |18+| `@zxing/library` | `^0.23.0` | `0.23.0` |19+| `bwip-js` | `^4.10.1` | `4.11.1` |20+| `pdf-lib` | `^1.17.1` | `1.17.1` |21+| `papaparse` | `^5.5.3` | `5.5.3` *(optional hunk — see note)* |22+23+Note `bwip-js`: the lockfile already resolved `4.11.1` (caret drift from the24+declared `^4.10.1`) — pinning to what is installed, per the brief.25+26+**papaparse (optional hunk):** the brief's pin list names five packages; the27+report's finding text additionally cites `papaparse ^5.5.3` (CSV import path —28+the formula-injection surface). The hunk below includes it for a uniform29+supply-chain posture; drop that one line if the integrator prefers to track30+the brief's list exactly.31+32+## package.json edit33+34+```json35+// BEFORE (package.json:40-46, interleaved with already-pinned deps)36+    "@zxing/library": "^0.23.0",37+    "bwip-js": "^4.10.1",38+    "hash-wasm": "4.12.0",39+    "otpauth": "^9.5.0",40+    "pako": "^2.1.0",41+    "papaparse": "^5.5.3",42+    "pdf-lib": "^1.17.1",43+44+// AFTER45+    "@zxing/library": "0.23.0",46+    "bwip-js": "4.11.1",47+    "hash-wasm": "4.12.0",48+    "otpauth": "9.5.0",49+    "pako": "2.1.0",50+    "papaparse": "5.5.3",51+    "pdf-lib": "1.17.1",52+```53+54+## Integration steps (cannot run from a draft — no build tools in this pass)55+56+1. Apply the `package.json` edit.57+2. `bun install` — regenerates `bun.lock` so the recorded ranges match the58+   pinned declarations. Because every pin equals the resolved version, the59+   dependency tree (and every integrity hash) is unchanged; only range60+   metadata moves.61+3. Verify: `git`-free check via `koh status --json` should show exactly62+   `package.json` + `bun.lock` modified; `bun pm ls | grep -E "otpauth|pako|zxing|bwip|pdf-lib"`63+   shows the same versions as before.64+4. `npx tsc --noEmit` && `npx vitest run` — no code paths change; both must65+   stay green trivially.66+67+Transitive note (no action): `pdf-lib` internally depends on `pako ^1.0.11`68+(its own compiled copy, resolved at `1.0.11` in the lockfile). That copy is69+internal to pdf-lib's API and is not the `[email protected]` Peach imports in70+`generate.ts`; the lockfile pins both today.71+72+## WASM-PROVENANCE.md — deviation note73+74+The brief says "add a comment to `WASM-PROVENANCE.md`", but **no such file75+exists anywhere in the repository** (searched repo-wide). The LV-9 audit-fix76+draft session referenced creating one; it evidently never landed. This draft77+therefore ships the file itself — see `WASM-PROVENANCE.md` in this folder,78+destination: repo root. If the integrator knows of an existing provenance doc79+under a different name, fold the "Exact-pin discipline" section into it80+instead.81+82+## Save message (integration)83+84+`fix: PCH-02-004 exact-pin crypto-adjacent deps (otpauth, pako, @zxing/library, bwip-js, pdf-lib, papaparse) + add WASM-PROVENANCE.md`

view file

src/drafts/PCH-02-FIXES/PCH-02-005-legacy-pbkdf2-doc.patch.md+118 −0
@@ -0,0 +1,118 @@1+# PCH-02-005 — Document the legacy PBKDF2 import KDF + recommend re-export2+3+**Severity:** Informational4+**Target files:** `src/shared/backup.ts:146` (`derivePeachExportKey`),5+`src/popup/views/Import.tsx` (re-export notice)6+**Report:** peach-code-review.md, Finding PCH-02-0057+8+## Correction to the fix brief9+10+The brief's comment text says "100,000 iterations". The code (and the report)11+say **250,000** (`backup.ts:166`). The comment below uses the value the code12+actually enforces — a comment that misstates its own parameters is worse than13+no comment.14+15+## Edit 1 — KDF comment at backup.ts:14616+17+```typescript18+// BEFORE (backup.ts:146-150)19+export async function derivePeachExportKey(20+  password: string,21+  salt: Uint8Array,22+  opts: { raw?: boolean } = {}23+): Promise<Uint8Array> {24+25+// AFTER26+/**27+ * LEGACY IMPORT KDF (PCH-02-005):28+ * This path derives with PBKDF2-SHA256 (250,000 iterations) for backwards29+ * compatibility with `.json` exports sealed before the Argon2id migration —30+ * the encrypted-JSON export UI was removed in v2.5.0 (see header), so this31+ * KDF is import-only (plus the test fixture generator above).32+ *33+ * PBKDF2 has no memory-hardness and is materially weaker than the current34+ * Argon2id envelope (m=64 MiB, t=3, p=4 — peachPayload.ts) against GPU/ASIC35+ * attackers at equal wall-clock cost. Do NOT use this helper for any new36+ * format. Users importing an old `.json` backup are prompted to export a37+ * fresh backup afterwards (Import.tsx legacy-KDF notice).38+ */39+export async function derivePeachExportKey(40+  password: string,41+  salt: Uint8Array,42+  opts: { raw?: boolean } = {}43+): Promise<Uint8Array> {44+```45+46+(The existing LV-1 NFC comment inside the function body stays as is.)47+48+## Edit 2 — post-import re-export notice in Import.tsx49+50+Verified: there is currently **no** user-facing recommendation after a51+successful PBKDF2 (`peach-json`) import. The import success path keeps the52+view mounted and already surfaces a post-import `duplicateWarning` notice53+(Import.tsx:790) — the legacy-KDF notice mirrors that exact mechanism.54+55+2a. Add a signal next to `duplicateWarning` (Import.tsx:~425):56+57+```typescript58+// AFTER the existing line:59+  const [duplicateWarning, setDuplicateWarning] = createSignal<number>(0);60+// ADD:61+  // PCH-02-005: set when a legacy PBKDF2-encrypted `.json` backup imports62+  // successfully — prompts the user to re-export under the Argon2id format.63+  const [legacyKdfNotice, setLegacyKdfNotice] = createSignal(false);64+```65+66+2b. In `handleImport`, alongside `setDuplicateWarning(dupes)` (the67+success tail, Import.tsx:~604):68+69+```typescript70+// BEFORE71+      setDuplicateWarning(dupes);72+      await props.onImport();73+74+// AFTER75+      setDuplicateWarning(dupes);76+      // PCH-02-005: "peach-json" is the legacy PBKDF2-SHA256 envelope; every77+      // other recognised format is plaintext-source (no KDF) or current-format.78+      setLegacyKdfNotice(currentPreview.format === "peach-json");79+      await props.onImport();80+```81+82+Also reset it where the other notices reset (`handleFileSelect`, alongside83+`setError(null)` / `setAwaitingReplaceConfirm(false)`):84+85+```typescript86+      setLegacyKdfNotice(false);87+```88+89+2c. Render next to the duplicate-warning block (Import.tsx:~790, same90+`<Show>` pattern and styling class as the sibling notice):91+92+```tsx93+        <Show when={legacyKdfNotice() && !importing()}>94+          <div class="peach-settings-group">95+            <div class="peach-restore-warning">96+              This backup was created with Peach's older encryption format97+              (PBKDF2). It imported fine — but we recommend exporting a fresh98+              backup now so your paper/file backups use the current, stronger99+              format.100+            </div>101+          </div>102+        </Show>103+```104+105+(Class choice: `peach-restore-warning` is the existing soft-warning surface106+used three blocks above; no new CSS.)107+108+## Tests109+110+UI-notice coverage belongs in `src/popup/views/contracts.test.tsx` style; the111+notice is a pure render-state change. Suggested single contract case (optional,112+not counted as a security regression test): mock `parseImportSource` to return113+`format: "peach-json"`, drive `handleImport`, assert the notice text renders.114+The KDF comment itself needs no test.115+116+## Save message (integration)117+118+`fix: PCH-02-005 document legacy PBKDF2 import path + recommend re-export after legacy import`

view file

src/drafts/PCH-02-FIXES/PCH-02-006-vault-id-privacy-note.patch.md+48 −0
@@ -0,0 +1,48 @@1+# PCH-02-006 — vault_id LAN-linkability privacy note (doc-only)2+3+**Severity:** Informational4+**Target file:** `src/shared/sync-identity.ts:62-67` (`computeVaultId` docblock)5+**Report:** peach-code-review.md, Finding PCH-02-0066+7+## Decision8+9+Documentation only. The property is inherent to peer discovery (peers must10+recognise "same vault" before exchanging entries); the fix is to state the11+privacy consequence where the identifier is minted, with the existing12+mitigation pointers.13+14+## Exact edit — extend the computeVaultId docblock15+16+```typescript17+// BEFORE (sync-identity.ts:62-67)18+/**19+ * Stable per-vault identifier carried in the mDNS TXT record and the20+ * first WebSocket handshake. Receiver rejects the connection if its21+ * own vault_id differs — prevents a paired-but-different-vault peer22+ * from corrupting the registry.23+ */24+25+// AFTER26+/**27+ * Stable per-vault identifier carried in the mDNS TXT record and the28+ * first WebSocket handshake. Receiver rejects the connection if its29+ * own vault_id differs — prevents a paired-but-different-vault peer30+ * from corrupting the registry.31+ *32+ * PRIVACY NOTE (PCH-02-006): vault_id is stable across sessions and is33+ * broadcast over mDNS during LAN sync discovery. A passive observer on the34+ * same network can therefore (a) link every device sharing this vault as35+ * belonging to one user, and (b) observe when sync activity occurs. This is36+ * inherent to the discovery design and is one-way — the master key is not37+ * recoverable from the BLAKE3 output. It never leaves the local network.38+ * Users who need LAN unlinkability should leave sync disabled; "rotate sync39+ * identity" (vault.ts rotateSyncIdentity) re-keys the root key and with it40+ * this identifier.41+ */42+```43+44+No tests — comment-only change.45+46+## Save message (integration)47+48+`docs: PCH-02-006 document vault_id LAN-linkability privacy note at computeVaultId`

view file

src/drafts/PCH-02-FIXES/WASM-PROVENANCE.md+57 −0
@@ -0,0 +1,57 @@1+# WASM & Crypto-Adjacent Dependency Provenance2+3+<!-- DESTINATION: repo root. Drafted under PCH-02-004 because the file the4+     LV-9 session referenced was never committed. -->5+6+Peach's vault security rests on a small set of third-party packages that7+either ship WebAssembly blobs or sit directly on a security path. This file8+records what each one is for and the supply-chain rules that apply to them.9+10+## Exact-pin discipline (LV-9, extended by PCH-02-004)11+12+Every dependency listed below is **exact-pinned** in `package.json` (no caret13+or tilde ranges) and locked in `bun.lock` with `packageManager` pinned to a14+specific bun release. CI installs with a frozen lockfile (LV-6). Upgrading any15+of them is a deliberate act: bump the pin, regenerate the lockfile, and re-run16+the full suite — never a side effect of a fresh resolve.17+18+### WASM-shipping packages19+20+| Package | Pinned | Role |21+|---|---|---|22+| `hash-wasm` | 4.12.0 | Argon2id (master-password KDF) and BLAKE3 (payload checksums, vault id) |23+| `@bokuweb/zstd-wasm` | 0.0.27 | zstd compression inside the encrypted export envelope |24+| `@subspace/reed-solomon-erasure.wasm` | 0.2.5 | Codex paper-backup erasure coding |25+26+### Pure-JS crypto core27+28+| Package | Pinned | Role |29+|---|---|---|30+| `@stablelib/*` (blake2s, chacha20poly1305, hkdf, hmac, sha256, x25519) | 2.0.1 | Noise XX/NK handshakes and transport encryption for LAN sync |31+| `@zxcvbn-ts/*` | 3.0.4 / 3.0.2 | Password strength estimation |32+| `tldts` | 7.0.28 | eTLD+1 derivation for the autofill origin boundary |33+| `pdfjs-dist` | 5.7.284 | Codex PDF import (hardened per MV-9) |34+35+### Crypto-adjacent (exact-pinned by PCH-02-004)36+37+These do not hold key material but feed or transform security-relevant data,38+so the same pin discipline applies:39+40+| Package | Pinned | Role |41+|---|---|---|42+| `otpauth` | 9.5.0 | TOTP code generation (RFC 6238) from stored seeds |43+| `pako` | 2.1.0 | Codex restore-side decompression (bomb-capped per MV-4) |44+| `@zxing/library` | 0.23.0 | Aztec matrix decode on Codex restore |45+| `bwip-js` | 4.11.1 | Aztec matrix encode on Codex generation |46+| `pdf-lib` | 1.17.1 | Codex PDF assembly (note: bundles its own internal `[email protected]`, distinct from the import-side `[email protected]`) |47+| `papaparse` | 5.5.3 | CSV import parsing (formula-injection surface) |48+49+## Verifying a WASM blob50+51+The WASM packages ship compiled binaries; their provenance chain is52+npm-registry integrity hash → `bun.lock` → frozen-lockfile CI. When bumping53+one, diff the package's published source repository tag against the shipped54+artifact where the upstream provides reproducible builds, and re-run the55+unlock-time Known-Answer self-test (`src/background/self-test.ts`) plus the56+Argon2id KAT (`src/crypto/argon2id.kat.test.ts`) — a silently-swapped KDF or57+hash implementation fails those vectors.

view file