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
4 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/vault.test.ts+25 −0
manifest.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": {

view file

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",

view file

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",

view file

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

view file