7c75b22961a9

fix: Bound cumulative locked sync inbox (v4.9.3)

time
2026-08-11 17:25 UTC · 2 months ago
machine
koh2
agent
gpt-5.6-sol
session
opaque locked background sync
parent
f5eeec05e167
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/locked-sync-spool.test.ts+24 −0src/background/locked-sync-spool.ts+6 −0
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "4.9.2",4+  "version": "4.9.3",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "4.9.2",5+  "version": "4.9.3",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+1 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "4.9.2",3+  "version": "4.9.3",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/background/locked-sync-spool.test.ts+24 −0
@@ -76,6 +76,30 @@7676     expect(stored.entries.remote).toBeUndefined();7777   });7878 79+  it("bounds the cumulative locked inbox across repeated sync sessions", async () => {80+    const vault = await import("./vault");81+    await vault.createVault("correct horse battery staple");82+    const { refreshLockedSyncSpool } = await import("./sync-vault-adapter");83+    await refreshLockedSyncSpool();84+    vault.lock();85+86+    const { createLockedSyncVaultAdapter } = await import("./locked-sync-spool");87+    const adapter = (await createLockedSyncVaultAdapter())!;88+    const stored = local.peach_locked_sync_spool_v1 as {89+      inboxEntries: Record<string, { modifiedAt: number; ciphertext: string }>;90+      inboxTombstones: Record<string, number>;91+    };92+    for (let index = 0; index < 100_000; index += 1) {93+      stored.inboxEntries[`entry-${index}`] = { modifiedAt: 1, ciphertext: "AA==" };94+      stored.inboxTombstones[`tombstone-${index}`] = 1;95+    }96+97+    await expect(adapter.applyEntries({ overflow: "AA==" }, { overflow: 2 }))98+      .rejects.toThrow("sync_entry_count_exceeded");99+    await expect(adapter.applyTombstones({ overflow: 2 }))100+      .rejects.toThrow("sync_tombstone_count_exceeded");101+  });102+79103   it("drains staged envelopes through the live decrypt boundary after unlock", async () => {80104     const vault = await import("./vault");81105     const syncAdapter = await import("./sync-vault-adapter");

view file

src/background/locked-sync-spool.ts+6 −0
@@ -126,6 +126,9 @@126126             inbox[id] = { modifiedAt, ciphertext };127127           }128128         }129+        if (Object.keys(inbox).length > SYNC_LIMITS.MAX_ENTRY_COUNT) {130+          throw new Error("sync_entry_count_exceeded");131+        }129132         await saveSpool({ ...current, inboxEntries: inbox });130133       });131134     },@@ -140,6 +143,9 @@140143           if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) continue;141144           const deletedAt = clampSyncTimestamp(value, now);142145           if ((inbox[id] ?? 0) < deletedAt) inbox[id] = deletedAt;146+        }147+        if (Object.keys(inbox).length > SYNC_LIMITS.MAX_TOMBSTONE_COUNT) {148+          throw new Error("sync_tombstone_count_exceeded");143149         }144150         await saveSpool({ ...current, inboxTombstones: inbox });145151       });

view file