0b702b354fb3
fix passkey backup eligibility flags across Chrome and Firefox ceremonies with regression coverage (PB-26-318, v5.0.87)
- time
- 2026-09-04 00:25 UTC · 1 month ago
- machine
- koh2
- agent
- claude-sonnet-4-6
- session
- PB-26-318 passkey backup flag
- parent
- 6d01667c8d96
↓ peach-browser-0b702b35.tar.gzatlas at this save
files changed6 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/passkey-chrome-ceremony.test.ts+5 −0src/background/passkey-firefox-proxy.test.ts+3 −0src/background/passkey.ts+5 −5manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.0.86",4+ "version": "5.0.87",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.0.86",5+ "version": "5.0.87",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.0.86",3+ "version": "5.0.87",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/passkey-chrome-ceremony.test.ts+5 −0
@@ -451,8 +451,13 @@451451 const stored = vault.getEntries().find((e) => e.passkey?.rpId === "github.com");452452 expect(stored).toBeDefined();453453 expect(stored!.passkey!.browser).toBe("chrome");454+ expect(stored!.passkey!.backupEligible).toBe(true);454455 expect(stored!.passkey!.signCount).toBe(0);455456 expect(stored!.passkey!.privateKeyJwk).toBeDefined();457+458+ const createResponse = JSON.parse(completes.create.find((c) => c.requestId === 124)!.responseJson!);459+ const authenticatorData = Uint8Array.from(atob(createResponse.response.authenticatorData.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(createResponse.response.authenticatorData.length / 4) * 4, "=")), (char) => char.charCodeAt(0));460+ expect(authenticatorData[32]! & 0x08).toBe(0x08);456461 });457462 458463 it("create attaches the passkey to an existing login entry for the same domain instead of creating a new one", async () => {src/background/passkey-firefox-proxy.test.ts+3 −0
@@ -470,6 +470,7 @@470470 expect(stored).toBeDefined();471471 expect(stored!.passkey!.signCount).toBe(0);472472 expect(stored!.passkey!.browser).toBe("firefox");473+ expect(stored!.passkey!.backupEligible).toBe(true);473474 474475 const clientDataGet = Array.from(new TextEncoder().encode(475476 JSON.stringify({ type: "webauthn.get", challenge, origin, crossOrigin: false })@@ -492,6 +493,8 @@492493 expect(assertion.type).toBe("get");493494 expect(typeof assertion.signature).toBe("string");494495 expect(typeof assertion.authenticatorData).toBe("string");496+ const authenticatorData = Uint8Array.from(atob(assertion.authenticatorData.replace(/-/g, "+").replace(/_/g, "/").padEnd(Math.ceil(assertion.authenticatorData.length / 4) * 4, "=")), (char) => char.charCodeAt(0));497+ expect(authenticatorData[32]! & 0x08).toBe(0x08);495498 496499 const after = vault.getEntries().find((e) => e.passkey?.rpId === "github.com");497500 expect(after!.passkey!.signCount).toBe(1);src/background/passkey.ts+5 −5
@@ -532,7 +532,7 @@532532 algorithm,533533 signCount: 0,534534 aaguid: base64urlEncode(new Uint8Array(16)),535- backupEligible: false,535+ backupEligible: true,536536 backupState: false,537537 browser: "chrome",538538 transports: ["internal"],@@ -1199,11 +1199,11 @@11991199 const { bytes: credentialIdBytes, base64url: credentialId } = generateCredentialId();12001200 const { publicKeyCose, publicKeySpki, privateKeyJwk } = await generatePasskeyKeypair(algorithm);12011201 1202- // 0x45 = UP|UV|AT (user-present + user-verified + attested-credential-data).1203- // Matches Chrome's create flag set in handleCreateRequest above.1202+ // 0x4d = UP|UV|BE|AT. Peach passkeys are synced, so BE is true on both1203+ // registration paths and on later assertions.12041204 const authenticatorData = await buildAuthenticatorData({12051205 rpId: payload.rpId,1206- flags: 0x45,1206+ flags: 0x4d,12071207 signCount: 0,12081208 credentialId: credentialIdBytes,12091209 publicKeyCose,@@ -1222,7 +1222,7 @@12221222 algorithm,12231223 signCount: 0,12241224 aaguid: base64urlEncode(new Uint8Array(16)),1225- backupEligible: false,1225+ backupEligible: true,12261226 backupState: false,12271227 browser: "firefox",12281228 transports: ["internal"],