22d6a1a0bb03

fix: apply capDeviceName to adoption-sourced PairedDevice records (audit PB-26-603, v5.1.13)

time
2026-09-06 14:59 UTC · 1 month ago
machine
koh2
agent
openrouter/tencent/hy4-preview
session
PB-26-601..611 audit closeout
parent
c067510e1a14
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/shared/vault-adopt.test.ts+62 −0src/shared/vault-adopt.ts+3 −3
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.1.12",4+  "version": "5.1.13",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": "5.1.12",5+  "version": "5.1.13",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": "5.1.12",3+  "version": "5.1.13",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/shared/vault-adopt.test.ts+62 −0
@@ -865,3 +865,65 @@865865     expect(legacyCombinedSize).toBeGreaterThan(CEILING);866866   });867867 });868+869+/**870+ * PB-26-603 — adoption frames carry a peer-supplied `device_name` straight into871+ * `chrome.storage.local`. The manifest path caps it at 64 chars; adoption did872+ * not, so one name could be ~16Kx larger than the manifest bound.873+ */874+describe("PB-26-603 adoption device_name cap", () => {875+  it("caps a 500-char peer device_name to 64 chars on both PairedDevice records", async () => {876+    const longName = "X".repeat(500);877+    const [receiverChannel, sourceChannel] = memoryPair();878+    const rootKeyHex = "13".repeat(32);879+    const vaultId = await computeVaultId(Uint8Array.from(Buffer.from(rootKeyHex, "hex")));880+    const receiverUpsert = vi.fn();881+    const sourceUpsert = vi.fn();882+883+    const receiverDeps: AdoptionReceiverDeps = {884+      deviceName: "receiver",885+      generateDeviceId: () => "receiver-ephemeral",886+      confirmSas: vi.fn(async () => true),887+      createVaultFromRootKey: vi.fn(async () => ({ vaultId })),888+      deriveLocalIdentity: vi.fn(async () => ({889+        deviceId: "receiver-id",890+        deviceName: longName,891+        deviceType: "extension" as const,892+        longTermPublicKeyHex: "ab".repeat(32),893+        signingPublicKeyHex: "cd".repeat(32),894+      })),895+      upsertPairedDevice: receiverUpsert,896+      applyLicense: vi.fn(),897+      onError: vi.fn(),898+    };899+    const sourceDeps: AdoptionSourceDeps = {900+      sourceIdentity: {901+        deviceId: "source-id",902+        deviceName: longName,903+        deviceType: "extension" as const,904+        longTermPublicKeyHex: "ef".repeat(32),905+        signingPublicKeyHex: "01".repeat(32),906+      },907+      rootKeyHex: vi.fn(async () => rootKeyHex),908+      vaultId: vi.fn(async () => vaultId),909+      license: vi.fn(async () => "PPK-test"),910+      onConsent: vi.fn(async () => true),911+      upsertPairedDevice: sourceUpsert,912+    };913+914+    await Promise.all([915+      requestAdoption(receiverChannel, receiverDeps),916+      (async () => {917+        const request = JSON.parse(918+          new TextDecoder().decode(await sourceChannel.receive()),919+        ) as AdoptRequest;920+        await grantAdoption(sourceChannel, request, sourceDeps);921+      })(),922+    ]);923+924+    const storedReceiver = receiverUpsert.mock.calls[0]?.[0] as { deviceName: string };925+    const storedSource = sourceUpsert.mock.calls[0]?.[0] as { deviceName: string };926+    expect(storedReceiver.deviceName).toBe(longName.slice(0, 64));927+    expect(storedSource.deviceName).toBe(longName.slice(0, 64));928+  });929+});

view file

src/shared/vault-adopt.ts+3 −3
@@ -1,4 +1,4 @@1-import { SYNC_LIMITS } from "./sync-limits";1+import { capDeviceName, SYNC_LIMITS } from "./sync-limits";22 import { computeVaultId } from "./sync-identity";33 import type { DeviceType, PairedDevice, SignedDeviceManifest } from "./sync-types";44 import type { LocalIdentity, PairingTransport } from "./pairing-handshake";@@ -651,7 +651,7 @@651651 652652   const sourceDevice: PairedDevice = {653653     deviceId: frame.source.device_id,654-    deviceName: frame.source.device_name,654+    deviceName: capDeviceName(frame.source.device_name),655655     deviceType: frame.source.device_type,656656     longTermPublicKeyHex: frame.source.long_term_public_key_hex,657657     signingPublicKeyHex: frame.source.signing_public_key_hex,@@ -720,7 +720,7 @@720720 721721   const device: PairedDevice = {722722     deviceId: identity.device_id,723-    deviceName: identity.device_name,723+    deviceName: capDeviceName(identity.device_name),724724     deviceType: identity.device_type,725725     longTermPublicKeyHex: identity.long_term_public_key_hex,726726     signingPublicKeyHex: identity.signing_public_key_hex,

view file