c9312ae6559e
fix: passkey allowCredentials match re-verifies the requesting rpId (PB-26-015) — a foreign-RP request listing a known credential id now passes through instead of selecting the credential, regression test; bump 5.0.5→5.0.6
- time
- 2026-08-30 00:05 UTC · 1 month ago
- machine
- koh2
- agent
- glm-5.3-flash
- session
- pb-26 audit fixes
- parent
- 9b95e9847dff
↓ peach-browser-c9312ae6.tar.gzatlas at this save
files changed5 files
manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/passkey-chrome-ceremony.test.ts+20 −0src/background/passkey.ts+4 −0manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22 "manifest_version": 3,33 "name": "Peach Password Manager",4- "version": "5.0.5",4+ "version": "5.0.6",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.5",5+ "version": "5.0.6",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.5",3+ "version": "5.0.6",44 "description": "Zero-knowledge password manager",55 "license": "GPL-3.0-only",66 "type": "module",src/background/passkey-chrome-ceremony.test.ts+20 −0
@@ -523,6 +523,26 @@523523 expect(done.responseJson).toBeUndefined();524524 });525525 526+ // PB-26-015: a credential-ID match must still belong to the requesting rpId —527+ // a foreign-RP request listing a known credential id must not select it.528+ it("get does not match a credential whose stored rpId differs from the request rpId", async () => {529+ const vault = await import("./vault");530+ await vault.createVault("correct-pass-12345");531+ const { credentialId } = await seedPasskey("github.com");532+533+ fireGet(534+ {535+ rpId: "evil.example",536+ challenge: "Y2g",537+ allowCredentials: [{ type: "public-key", id: credentialId }],538+ },539+ 205,540+ );541+ const done = await waitForGetComplete(205);542+ expect(done.error).toBeUndefined();543+ expect(done.responseJson).toBeUndefined();544+ });545+526546 it("get encodes the active tab page origin into clientDataJSON (not https://<rpId>)", async () => {527547 const vault = await import("./vault");528548 await vault.createVault("correct-pass-12345");src/background/passkey.ts+4 −0
@@ -679,6 +679,10 @@679679 const allowedCredentialIds = (options.allowCredentials ?? []).map((descriptor) => descriptor.id);680680 681681 const match = passkeyBearing.find((entry) => {682+ // PB-26-015: a credential-ID match must still belong to the requesting683+ // RP — the assertion later signs with the stored rpId, so a foreign-RP684+ // request must never select a credential from a different rpId.685+ if (options.rpId && entry.passkey.rpId !== options.rpId) return false;682686 if (allowedCredentialIds.length > 0) {683687 return allowedCredentialIds.includes(entry.passkey.credentialId);684688 }