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
↓ peach-browser-7c75b229.tar.gzatlas at this save
files changed5 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 −0manifest.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": {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",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",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");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 });