b5de3f75e43a
fix(sync): sync_received writes do not advance vault version — no-op sync optimisation restored, prevents ping-pong (v3.6.112)
- time
- 2026-05-28 20:07 UTC · 5 months ago
- machine
- koh2
- parent
- 19da2c5da38d
↓ peach-browser-b5de3f75.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/vault.test.ts+37 −0src/background/vault.ts+8 −1manifest.firefox.json+1 −1
@@ -2,7 +2,7 @@22 "_comment_audio_capture": "Firefox does not honor the Chrome 'audioCapture' optional_permission — getUserMedia in WebExtensions is gated solely by the runtime permission prompt. Microphone access for chirp pairing works from the popup under the existing '<all_urls>' host permission; no manifest entry is required or accepted here.",33 "manifest_version": 3,44 "name": "Peach Password Manager",5- "version": "3.6.111",5+ "version": "3.6.112",66 "description": "Zero-knowledge password manager",77 "browser_specific_settings": {88 "gecko": {manifest.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "3.6.111",4+ "version": "3.6.112",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": "3.6.111",3+ "version": "3.6.112",44 "description": "Zero-knowledge password manager",55 "type": "module",66 "scripts": {src/background/vault.test.ts+37 −0
@@ -932,4 +932,41 @@932932 const restored = vault.getEntries().find((e) => e.id === note.id);933933 expect(restored?.passwordChangedAt).toBeUndefined();934934 });935+936+ it("applyMergedRemoteEntries does not advance the vault version", async () => {937+ const vault = await import("./vault");938+ await vault.createVault("syncver-pass-aaaaa");939+ await vault.addEntry({ title: "Local", username: "june", password: "p", url: "https://l.test" });940+941+ const before = await vault.getVaultVersion();942+ await new Promise((r) => setTimeout(r, 2));943+ await vault.applyMergedRemoteEntries(944+ [945+ {946+ id: "remote-1",947+ type: "login",948+ title: "Remote",949+ username: "r",950+ password: "x",951+ url: "https://r.test",952+ createdAt: Date.now(),953+ modifiedAt: Date.now(),954+ },955+ ],956+ {},957+ );958+959+ expect(await vault.getVaultVersion()).toBe(before);960+ });961+962+ it("addEntry advances the vault version", async () => {963+ const vault = await import("./vault");964+ await vault.createVault("syncver-pass-bbbbb");965+966+ const before = await vault.getVaultVersion();967+ await new Promise((r) => setTimeout(r, 2));968+ await vault.addEntry({ title: "Local", username: "june", password: "p", url: "https://l.test" });969+970+ expect(await vault.getVaultVersion()).toBeGreaterThan(before);971+ });935972 });src/background/vault.ts+8 −1
@@ -1847,7 +1847,14 @@18471847 if (!duressConfigured) {18481848 await writeSlot(SLOT_B, generateNoiseBlob(ciphertext.length / 2));18491849 }1850- await stampLocalVaultMutation();1850+ // Only advance the vault version for locally-originated writes. Entries1851+ // applied from a sync peer ("sync_received") must NOT bump the counter —1852+ // otherwise applying a remote round makes us look newer than the peer and1853+ // triggers a redundant outbound sync, defeating the no-op optimisation1854+ // and risking a ping-pong between two devices (SEV-2-C).1855+ if (reason !== "sync_received") {1856+ await stampLocalVaultMutation();1857+ }18511858 18521859 return nextSlotA;18531860 }