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
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/background/passkey-chrome-ceremony.test.ts+20 −0src/background/passkey.ts+4 −0
manifest.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": {

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.0.5",5+  "version": "5.0.6",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.0.5",3+  "version": "5.0.6",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

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");

view file

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     }

view file