22016a5dbc67
pin that the duress vault derives no root key, so every sync operation fails closed and no second vault id exists (v4.1.7)
- time
- 2026-07-26 08:51 UTC · 3 months ago
- machine
- koh2
- agent
- claude-opus-5
- session
- sync campaign remaining items
- parent
- 158d6d9fe504
↓ peach-browser-22016a5d.tar.gzatlas at this save
files changed4 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/vault.test.ts+25 −0manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.1.6",4+ "version": "4.1.7",55 "description": "Zero-knowledge password manager",66 "browser_specific_settings": {77 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.1.6",4+ "version": "4.1.7",55 "description": "Zero-knowledge password manager",66 "action": {77 "default_popup": "src/popup/index.html",package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "4.1.6",3+ "version": "4.1.7",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/vault.test.ts+25 −0
@@ -155,6 +155,31 @@155155 expect(vault.getEntries()).toEqual([]);156156 });157157 158+ it("HOLE 5: the duress vault has no sync identity and cannot sync", async () => {159+ const vault = await import("./vault");160+ await vault.createVault("real-password-12345");161+ await vault.addEntry({ type: "login", title: "GitHub", password: "realsecret" });162+ await vault.setupDuress("decoy-password-67890");163+164+ // Baseline: the real vault does have a sync identity.165+ expect(vault.getRootKeyHex()).toBeTruthy();166+ vault.lock();167+168+ expect(await vault.unlock("decoy-password-67890")).toBe(true);169+ // A second vault_id would be a duress indicator: a coerced user opening the170+ // decoy would make sync visibly fail on their other devices. The decoy path171+ // never derives a root key, so there is nothing to derive one from.172+ expect(vault.getRootKeyHex()).toBeNull();173+174+ // Every sync adapter operation therefore fails closed rather than syncing,175+ // manifesting, or revealing an entry count.176+ const { createSyncVaultAdapter } = await import("./sync-vault-adapter");177+ const adapter = createSyncVaultAdapter();178+ await expect(adapter.computeVaultId()).rejects.toThrow();179+ await expect(adapter.buildOutgoingDeviceManifest?.()).rejects.toThrow();180+ vault.lock();181+ });182+158183 it("runs both slot derivations in parallel for constant-time unlock (DUR-002)", async () => {159184 // The unlock path must derive keys for both slots unconditionally so a160185 // real-mode unlock and a decoy-mode unlock both spend roughly the same