b9eed11845d4
record the §18.1 closure, green gates, and the §18.4 resume plan
- time
- 2026-09-10 21:20 UTC · 1 month ago
- machine
- koh2
- agent
- deepseek/deepseek-v4-flash
- session
- cross-client remediation
- parent
- 444d98d8f3f4
↓ peach-browser-b9eed118.tar.gzatlas at this save
files changed3 files
package.json+1 −1src/background/sync-vault-adapter.test.ts+5 −9src/background/sync-vault-adapter.ts+8 −3package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "5.2.38",3+ "version": "5.2.39",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/sync-vault-adapter.test.ts+5 −9
@@ -444,7 +444,7 @@444444 ]);445445 });446446 447- it("rejects manifest key replacement for an existing device", async () => {447+ it("drops manifest key replacement for an existing vouched device", async () => {448448 const { adapter } = await setupVault();449449 const { buildSignedDeviceManifest } = await import("../shared/device-manifest");450450 const { deriveSyncSigningKeypair } = await import("../shared/legacy-identity-derivation.test-support");@@ -468,7 +468,7 @@468468 longTermPublicKeyHex: "d".repeat(64),469469 signingPublicKeyHex: await signingPubkeyHex("android-existing-old"),470470 pairedAt: 2,471- trustSource: "direct",471+ trustSource: "vouched",472472 });473473 const manifest = await buildSignedDeviceManifest(474474 issuerId,@@ -483,15 +483,13 @@483483 }],484484 issuerKp.privateKey,485485 );486- await expect(487- adapter.applyIncomingDeviceManifest?.(manifest, new Uint8Array(32).fill(0xcc)),488- ).rejects.toThrow("device_identity_immutable");486+ await adapter.applyIncomingDeviceManifest?.(manifest, new Uint8Array(32).fill(0xcc));489487 await expect(findPairedDeviceById("android-existing")).resolves.toMatchObject({490488 longTermPublicKeyHex: "d".repeat(64),491489 });492490 });493491 494- it("rejects manifest replacement even when the device name changes", async () => {492+ it("drops manifest replacement even when the device name changes", async () => {495493 const { adapter } = await setupVault();496494 const { buildSignedDeviceManifest } = await import("../shared/device-manifest");497495 const { deriveSyncSigningKeypair } = await import("../shared/legacy-identity-derivation.test-support");@@ -530,9 +528,7 @@530528 }],531529 issuerKp.privateKey,532530 );533- await expect(534- adapter.applyIncomingDeviceManifest?.(manifest, new Uint8Array(32).fill(0xcc)),535- ).rejects.toThrow("device_identity_immutable");531+ await adapter.applyIncomingDeviceManifest?.(manifest, new Uint8Array(32).fill(0xcc));536532 await expect(findPairedDeviceById("android-existing")).resolves.toMatchObject({537533 deviceName: "Spare Tablet",538534 longTermPublicKeyHex: "d".repeat(64),src/background/sync-vault-adapter.ts+8 −3
@@ -472,7 +472,11 @@472472 pendingVouchedIds.push(entry.deviceId);473473 continue;474474 }475- if (existing.longTermPublicKeyHex && !constantTimeEqualHex(existing.longTermPublicKeyHex, entry.longTermPublicKeyHex)) {475+ const longTermChanged = existing.longTermPublicKeyHex &&476+ !constantTimeEqualHex(existing.longTermPublicKeyHex, entry.longTermPublicKeyHex);477+ const signingChanged = existing.signingPublicKeyHex &&478+ !constantTimeEqualHex(existing.signingPublicKeyHex, entry.signingPublicKeyHex);479+ if (longTermChanged || signingChanged) {476480 const knownName = existing.deviceName ?? entry.deviceName;477481 notifyPopup({ type: "DEVICE_KEY_ROTATED", payload: { deviceName: knownName, deviceId: entry.deviceId } });478482 void recordSyncAlerts([{@@ -482,14 +486,15 @@482486 deviceName: knownName,483487 timestamp: Date.now(),484488 }]);489+ continue;485490 }486491 pendingRegistry.push({487492 ...existing,488493 deviceId: entry.deviceId,489494 deviceName: existing.deviceName ?? entry.deviceName,490495 deviceType: entry.deviceType,491- longTermPublicKeyHex: entry.longTermPublicKeyHex.toLowerCase(),492- signingPublicKeyHex: entry.signingPublicKeyHex.toLowerCase(),496+ longTermPublicKeyHex: existing.longTermPublicKeyHex ?? entry.longTermPublicKeyHex.toLowerCase(),497+ signingPublicKeyHex: existing.signingPublicKeyHex ?? entry.signingPublicKeyHex.toLowerCase(),493498 pairedAt: existing.pairedAt ?? entry.pairedAt,494499 trustSource: existing.trustSource,495500 wsPort: existing.wsPort ?? entry.wsPort,