edc59d2b2a37

add Firefox MAIN-world passkey detector + manifest content_scripts entry

time
2026-04-28 18:29 UTC · 6 months ago
machine
koh2
parent
698feaf6f97c
2 files manifest.firefox.json+13 −0src/content/passkeys-firefox.ts+70 −0
manifest.firefox.json+13 −0
@@ -29,7 +29,20 @@2929         "src/content/index.ts"3030       ],3131       "run_at": "document_idle"32+    },33+    {34+      "matches": [35+        "<all_urls>"36+      ],37+      "js": [38+        "src/content/passkeys-firefox.ts"39+      ],40+      "run_at": "document_start",41+      "world": "MAIN"3242     }43+  ],44+  "host_permissions": [45+    "<all_urls>"3346   ],3447   "commands": {3548     "_execute_action": {

view file

src/content/passkeys-firefox.ts+70 −0
@@ -0,0 +1,70 @@1+/**2+ * passkeys-firefox.ts — MAIN-world detection layer for Firefox 150+.3+ *4+ * Firefox does not expose `chrome.webAuthenticationProxy`, so Peach cannot5+ * intercept WebAuthn calls the way it does on Chrome. Instead, this script6+ * runs in the page's main world (per `manifest.firefox.json`'s `world: "MAIN"`)7+ * and monkey-patches `navigator.credentials.create` / `.get` to *observe*8+ * passkey activity and emit CustomEvents the rest of Peach can react to.9+ *10+ * The original calls always proceed unchanged. Peach never blocks, modifies,11+ * or replaces the page's own WebAuthn flow from here. Registration and12+ * authentication on behalf of the user happen separately, from the popup13+ * context, where Firefox 150+ allows extensions to call `navigator.credentials`14+ * directly with their own host-permitted RP ID.15+ *16+ * Events emitted:17+ *   `peach:passkey-create` — page invoked navigator.credentials.create with18+ *                            a publicKey option set. detail = { rpId, userName }19+ *   `peach:passkey-get`    — page invoked navigator.credentials.get with a20+ *                            publicKey option set. detail = { rpId, allowCredentials }21+ */22+23+(() => {24+  const credentials = navigator.credentials;25+  if (!credentials || typeof credentials.create !== "function" || typeof credentials.get !== "function") {26+    return;27+  }28+29+  const FLAG = "__peachPasskeysFirefoxAttached";30+  const w = window as unknown as Record<string, unknown>;31+  if (w[FLAG]) return;32+  w[FLAG] = true;33+34+  const originalCreate = credentials.create.bind(credentials);35+  const originalGet = credentials.get.bind(credentials);36+37+  credentials.create = async function patchedCreate(options) {38+    const publicKey = options?.publicKey;39+    if (publicKey) {40+      try {41+        window.dispatchEvent(42+          new CustomEvent("peach:passkey-create", {43+            detail: {44+              rpId: publicKey.rp?.id ?? location.hostname,45+              userName: publicKey.user?.name,46+            },47+          })48+        );49+      } catch { /* dispatching must never break the page */ }50+    }51+    return originalCreate(options);52+  };53+54+  credentials.get = async function patchedGet(options) {55+    const publicKey = options?.publicKey;56+    if (publicKey) {57+      try {58+        window.dispatchEvent(59+          new CustomEvent("peach:passkey-get", {60+            detail: {61+              rpId: publicKey.rpId ?? location.hostname,62+              allowCredentials: publicKey.allowCredentials,63+            },64+          })65+        );66+      } catch { /* dispatching must never break the page */ }67+    }68+    return originalGet(options);69+  };70+})();

view file