a22c047efc1a

preserve Firefox conditional passkey request timing (v5.3.5)

time
2026-10-08 15:51 UTC · 2 weeks ago
machine
Junes-MacBook-Pro.local
agent
gpt-6
session
repair firefox passkey sign-in
parent
69251a167183
5 files manifest.firefox.json+1 −1manifest.json+1 −1package.json+1 −1src/content/passkeys-firefox.test.ts+15 −1src/content/passkeys-firefox.ts+2 −1
manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.3.4",4+  "version": "5.3.5",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.3.4",5+  "version": "5.3.5",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.3.4",3+  "version": "5.3.5",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",

view file

src/content/passkeys-firefox.test.ts+15 −1
@@ -134,6 +134,7 @@134134 // ── tests ──────────────────────────────────────────────────────────────────135135 136136 describe("PB-26-209: exportFunction privileged bridge", () => {137+  let nativeGet: ReturnType<typeof vi.fn>;137138   beforeEach(async () => {138139     vi.resetModules();139140     setupChromeMock();@@ -150,7 +151,7 @@150151     // jsdom doesn't have navigator.credentials by default — install a151152     // native stub that the bridge will replace via exportFunction.152153     const nativeCreate = vi.fn(async () => null);153-    const nativeGet = vi.fn(async () => null);154+    nativeGet = vi.fn(async () => null);154155     Object.defineProperty(navigator, "credentials", {155156       value: { create: nativeCreate, get: nativeGet },156157       configurable: true,@@ -328,6 +329,19 @@328329   });329330 330331   describe("valid responses produce credentials", () => {332+    it("get: leaves conditional requests and their cancellation signal with Firefox", async () => {333+      const controller = new AbortController();334+      const options: CredentialRequestOptions = {335+        ...makeGetOptions(), mediation: "conditional", signal: controller.signal,336+      };337+      mockSendMessage338+        .mockResolvedValueOnce({ ok: true, hasCredentials: true })339+        .mockResolvedValueOnce({ ok: true, credential: validGetCredential() });340+      await expect(navigator.credentials.get(options)).resolves.toBeNull();341+      expect(nativeGet).toHaveBeenCalledWith(options);342+      expect(mockSendMessage).not.toHaveBeenCalled();343+    });344+331345     it.each([332346       "new Uint8Array([1, 2, 3, 4]).buffer",333347       "new Uint8Array(new Uint8Array([9, 1, 2, 3, 4, 9]).buffer, 1, 4)",

view file

src/content/passkeys-firefox.ts+2 −1
@@ -162,7 +162,8 @@162162   exportFunction(163163     function patchedGet(options?: CredentialRequestOptions): Promise<Credential | null> {164164       return pagePromise(async () => {165-        if (!options?.publicKey) return originalGet(options);165+        // Conditional autofill must remain pending under Firefox's UI and cancellation rules.166+        if (!options?.publicKey || options.mediation === "conditional") return originalGet(options);166167 167168         try {168169           const publicKey = options.publicKey;

view file