de56d93b26ce
fix: treat wsPort/relayUrl null same as undefined in manifest validation + normalise (v4.7.9)
- time
- 2026-08-01 17:25 UTC · 2 months ago
- machine
- koh2
- parent
- ceeb0d1edf66
↓ peach-browser-de56d93b.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/shared/device-manifest.test.ts+12 −0src/shared/device-manifest.ts+4 −4manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "4.7.8",4+ "version": "4.7.9",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.7.8",5+ "version": "4.7.9",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.7.8",3+ "version": "4.7.9",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/shared/device-manifest.test.ts+12 −0
@@ -251,4 +251,16 @@251251 const body = await verifyDeviceManifest(m, kp.publicKeyRaw);252252 expect(body.devices[0].wsPort).toBe(0);253253 });254+255+ it("does not reject wsPort:null on the wire (Android nullable Int → JSON null)", async () => {256+ const kp = await deriveSyncSigningKeypair(MASTER_KEY, ISSUER_DEVICE_ID);257+ // Build a valid manifest first, then inject wsPort:null into the body258+ // to simulate what Android puts on the wire (nullable Int serialised as259+ // JSON null). The canonicaliser strips null, so the signature still260+ // verifies — but the validator must not throw on the raw parsed body.261+ const m = await buildSignedDeviceManifest(ISSUER_DEVICE_ID, [entry()], kp.privateKey);262+ (m.body.devices[0] as unknown as Record<string, unknown>).wsPort = null;263+ const body = await verifyDeviceManifest(m, kp.publicKeyRaw);264+ expect(body.devices).toHaveLength(1);265+ });254266 });src/shared/device-manifest.ts+4 −4
@@ -151,7 +151,7 @@151151 throw new DeviceManifestError("malformed", "device.signingPublicKeyHex invalid");152152 }153153 if (154- d.wsPort !== undefined &&154+ d.wsPort != null &&155155 (156156 typeof d.wsPort !== "number" ||157157 !Number.isInteger(d.wsPort) ||@@ -161,7 +161,7 @@161161 ) {162162 throw new DeviceManifestError("malformed", "device.wsPort invalid");163163 }164- if (d.relayUrl !== undefined) {164+ if (d.relayUrl != null) {165165 try {166166 normalizeRelayUrl(d.relayUrl);167167 } catch {@@ -217,8 +217,8 @@217217 signingPublicKeyHex: d.signingPublicKeyHex.toLowerCase(),218218 pairedAt: d.pairedAt,219219 };220- if (d.wsPort !== undefined) normalised.wsPort = d.wsPort;221- if (d.relayUrl !== undefined) normalised.relayUrl = normalizeRelayUrl(d.relayUrl);220+ if (d.wsPort != null) normalised.wsPort = d.wsPort;221+ if (d.relayUrl != null) normalised.relayUrl = normalizeRelayUrl(d.relayUrl);222222 return normalised;223223 }224224