9aec7adfb69c
fix peer revoke and reset identity migration failures (v5.2.47)
- time
- 2026-09-11 17:01 UTC · 1 month ago
- machine
- koh2
- agent
- camel-stream/auto
- session
- fix peer revoke reset
- parent
- b8afc9544b71
↓ peach-browser-9aec7adf.tar.gzatlas at this save
files changed7 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/device-identity.ts+6 −8src/background/messages.test.ts+27 −2src/background/messages.ts+12 −4src/shared/release-notes.ts+8 −0manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.2.46",4+ "version": "5.2.47",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": "5.2.46",5+ "version": "5.2.47",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": "5.2.46",3+ "version": "5.2.47",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/device-identity.ts+6 −8
@@ -229,17 +229,15 @@229229 */230230 setGenerationIdentity({231231 async ensureIdentity() {232+ if (await hasLegacyTrustState(browserGenerationIo)) return false;232233 try {233- await buildLocalIdentity();234+ // A secure reset retires the old identity generation as well as its235+ // paired-device registry. Reusing the old record would let archived236+ // trust material identify this installation after the reset.237+ await rotateDeviceId();234238 return true;235239 } catch {236- if (await hasLegacyTrustState(browserGenerationIo)) return false;237- try {238- await rotateDeviceId();239- return true;240- } catch {241- return false;242- }240+ return false;243241 }244242 },245243 deviceId: getOrCreateDeviceId,src/background/messages.test.ts+27 −2
@@ -1753,7 +1753,31 @@17531753 revokedByDeviceId: expect.any(String),17541754 });17551755 });1756+ it("UNPAIR_DEVICE still evicts a peer when the local identity needs migration", async () => {1757+ const vault = await import("./vault");1758+ await vault.createVault("correct-pass-12345");1759+ const identity = await import("./device-identity");1760+ await identity.getOrCreateDeviceId();1761+ delete storage.peach_local_identity_v4;1762+ delete sessionStorage.peach_local_identity_session;1763+ const { findPairedDeviceById, isDeviceRevoked, upsertPairedDevice } = await import("./paired-devices");1764+ await upsertPairedDevice({1765+ deviceId: "phone-1",1766+ deviceName: "June's Phone",1767+ deviceType: "android",1768+ longTermPublicKeyHex: "a".repeat(64),1769+ signingPublicKeyHex: "b".repeat(64),1770+ pairedAt: 1_700_000_000_000,1771+ trustSource: "direct",1772+ });17561773 1774+ const response = await dispatch({ type: "UNPAIR_DEVICE", payload: { deviceId: "phone-1" } });1775+1776+ expect(response).toMatchObject({ ok: true });1777+ expect(await findPairedDeviceById("phone-1")).toBeNull();1778+ expect(await isDeviceRevoked("phone-1", "a".repeat(64))).toBe(true);1779+ });1780+17571781 it("UNPAIR_DEVICE on an unknown id is a no-op (ok) and writes no revocation", async () => {17581782 const vault = await import("./vault");17591783 await vault.createVault("correct-pass-12345");@@ -1783,11 +1807,11 @@17831807 });17841808 expect(res).toMatchObject({ ok: false, error: "invalid_payload" });17851809 });1786- it("SECURE_RESET archives legacy trust and reaches the new Sync anchor", async () => {1810+ it("SECURE_RESET archives legacy trust, rotates identity, and reaches the new Sync anchor", async () => {17871811 const vault = await import("./vault");17881812 await vault.createVault("correct-pass-12345");17891813 const identity = await import("./device-identity");1790- await identity.getOrCreateDeviceId();1814+ const previousDeviceId = await identity.getOrCreateDeviceId();17911815 const { upsertPairedDevice } = await import("./paired-devices");17921816 await upsertPairedDevice({17931817 deviceId: "phone-1",@@ -1805,6 +1829,7 @@18051829 });18061830 18071831 expect(response).toMatchObject({ ok: true, data: { state: "V2_ANCHOR" } });1832+ expect(await identity.getOrCreateDeviceId()).not.toBe(previousDeviceId);18081833 });18091834 18101835 // The old "fresh direct re-pair clears the old revocation" test dispatchedsrc/background/messages.ts+12 −4
@@ -1665,10 +1665,18 @@16651665 if (typeof deviceId !== "string" || !deviceId) {16661666 return { ok: false, error: "invalid_payload" };16671667 }1668- // Removing a device REVOKES it rather than just dropping the local1669- // row: a plain removal is undone the moment any other peer vouches it1670- // back through a manifest, which is exactly what the relay makes easy.1671- await revokeDeviceByIdV3(deviceId, await getOrCreateDeviceId());1668+ // A damaged legacy identity must not prevent the user from evicting a1669+ // peer locally. Sync is already blocked until the secure reset repairs1670+ // this installation, so the local issuer marker is sufficient for the1671+ // tombstone that will be cleared with the archived generation.1672+ let revokedByDeviceId: string;1673+ try {1674+ revokedByDeviceId = await getOrCreateDeviceId();1675+ } catch (error) {1676+ if (!(error instanceof Error) || error.message !== "identity_migration_required") throw error;1677+ revokedByDeviceId = "local";1678+ }1679+ await revokeDeviceByIdV3(deviceId, revokedByDeviceId);16721680 // PB-26-213: forget the secret now so no further dial can use it.16731681 // Removals that happen while locked leave orphans for the next16741682 // unlock's reconcile to prune.src/shared/release-notes.ts+8 −0
@@ -21,6 +21,14 @@2121 2222 export const RELEASES: PeachRelease[] = [2323 {24+ version: "5.2.47",25+ releasedAt: "2026-09-11",26+ notes: [{27+ heading: "Repair Sync reset and peer removal",28+ body: "Sync reset now creates the new device identity, and removing a peer still works when the old identity needs migration.",29+ }],30+ },31+ {2432 version: "5.2.46",2533 releasedAt: "2026-09-11",2634 importance: "breaking",