85dc226e3d80

guard the decoy snapshot write path and correct the gate contract (v5.2.45)

time
2026-09-11 04:48 UTC · 1 month ago
machine
koh2
agent
deepseek/deepseek-v4-flash
session
assessment findings remediation
parent
00d35eb4423f
3 files src/background/messages.ts+4 −0src/background/vault-history.test.ts+7 −0src/background/vault.ts+5 −1
src/background/messages.ts+4 −0
@@ -1254,6 +1254,10 @@12541254         // !! IMPORTANT: intentionally NOT Pro-gated. A free device must still12551255         // be able to restore a vault that was printed by a Pro user.12561256         // DO NOT add a Pro gate here.1257+        // A decoy session, though, holds no real-mode authority: this path1258+        // writes SLOT_A/SLOT_B and snapshots history, so it carries the same1259+        // guard its thirteen neighbours do (PB-26-915).1260+        if (vault.isDecoyMode()) return { ok: false, error: "vault_locked" };12571261         const payload = msg.payload as CodexRestorePayload;12581262         return await handleRestoreFromCodex(payload);12591263       }

view file

src/background/vault-history.test.ts+7 −0
@@ -243,6 +243,13 @@243243       error: "vault_locked",244244     });245245 246+    // Write side: the snapshot writer itself refuses in decoy mode, so a decoy247+    // session cannot seed or evict real-mode history through any caller —248+    // including paths that carry no decoy guard of their own.249+    const historyBefore = JSON.stringify(storage.peach_vault_history);250+    await vault.takeVaultSnapshot("local_save");251+    expect(JSON.stringify(storage.peach_vault_history)).toBe(historyBefore);252+246253     // Nothing was touched, and the real history survives intact.247254     expect(JSON.stringify(storage.peach_vault_a)).toBe(realSlots);248255     await vault.lock();

view file

src/background/vault.ts+5 −1
@@ -3328,6 +3328,10 @@33283328 export async function takeVaultSnapshot(33293329   reason: VaultSnapshot["reason"],33303330 ): Promise<void> {3331+  // The history store is real-mode state by construction, so it must not be3332+  // written from a decoy session either. The guard lives here rather than only3333+  // at the call sites so the invariant survives a new caller (PB-26-915).3334+  if (vaultMode === "decoy") return;33313335   try {33323336     const raw = await chrome.storage.local.get([SLOT_A, SLOT_B, HISTORY_KEY]);33333337     const slotA = (raw as Record<string, StoredVault | undefined>)[SLOT_A];@@ -3409,7 +3413,7 @@34093413 export async function getVaultHistory(): Promise<VaultSnapshot[]> {34103414   // The invariant lives here as well as at the message handlers, so it does not34113415   // depend on every caller remembering it — mirroring how takeVaultSnapshot3412-  // already refuses to record while in decoy mode (PB-26-915).3416+  // refuses to record while in decoy mode (PB-26-915).34133417   if (vaultMode === "decoy") return [];34143418   const raw = await chrome.storage.local.get(HISTORY_KEY);34153419   const history = (raw as Record<string, VaultHistory | undefined>)[HISTORY_KEY];

view file