2bbd5cf9fdad
fail closed when a revocation issuer cannot be resolved, matching Android and the documented contract (v5.2.35)
- time
- 2026-09-10 17:41 UTC · 1 month ago
- machine
- koh2
- agent
- deepseek/deepseek-v4-flash
- session
- cross-client remediation
- parent
- 0099d0e2d527
↓ peach-browser-2bbd5cf9.tar.gzatlas at this save
files changed2 files
package.json+1 −1src/background/sync-vault-adapter.ts+8 −5package.json+1 −1
@@ -1,6 +1,6 @@11 {22 "name": "peach",3- "version": "5.2.34",3+ "version": "5.2.35",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/sync-vault-adapter.ts+8 −5
@@ -420,7 +420,10 @@420420 if (body.issuerDeviceId !== issuer.deviceId) {421421 return;422422 }423- const verifiedRevocations = (423+ // Fail closed: an unresolvable or unverifiable issuer rejects the whole424+ // manifest rather than dropping just that record, so a pruned history entry425+ // can never silently un-revoke a device. Android enforces the same rule.426+ const allRevocationsAuthorised = (424427 await Promise.all(425428 body.revoked.map(async (record) => {426429 // The outer manifest is authenticated by the directly connected@@ -429,13 +432,13 @@429432 // immutable signing-key history; never bind it to the forwarder's key.430433 const originalIssuer = await findPairedDeviceById(record.revokedByDeviceId);431434 for (const key of trustedSigningKeys(originalIssuer)) {432- if (await verifyDeviceRevocation(record, hexToPubkey(key.publicKeyHex))) return record;435+ if (await verifyDeviceRevocation(record, hexToPubkey(key.publicKeyHex))) return true;433436 }434- return null;437+ return false;435438 }),436439 )437- ).filter((record): record is DeviceRevocation => record !== null);438- body = { ...body, revoked: verifiedRevocations };440+ ).every(Boolean);441+ if (!allRevocationsAuthorised) return;439442 440443 const ourDeviceId = await getOrCreateDeviceId();441444 const masterKey = requireMasterKey();