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
↓ peach-browser-85dc226e.tar.gzatlas at this save
files changed3 files
src/background/messages.ts+4 −0src/background/vault-history.test.ts+7 −0src/background/vault.ts+5 −1src/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 }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();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];