Firefox: related-origin passkeys silently fall back to the browser (FetcherError in related-origins-fetcher)
Setup
Firefox 155.0.1 on Linux, 1Password in the browser 8.12.32.33 (current AMO build). Firefox on any OS is affected. Chrome is not.
Symptom
On a site that uses WebAuthn related origins, 1Password takes over navigator.credentials.get() and then hands it back to Firefox without showing anything. The user gets Firefox's native prompt; on Linux that is only "Touch your security key". Our case: page origin https://app.example.com, rpId auth.example.com, https://auth.example.com/.well-known/webauthn served with 200, application/json, access-control-allow-origin: *, and https://app.example.com listed in origins.
Background console:
[Webauthn] Failed to core.webAuthnRPValidate: ⛔ Core error code: {"type":"FetcherError"}
[Webauthn] Failed to _handleGetCredential: bad-rp-id
[Webauthn] Failed to _handleGetCredential while unlocked: bad-rp-id
No Related-origins fetch failed warning is logged.
Cause
background/webauthn/related-origins-fetcher.ts, around lines 75 to 87. The fetcher wraps the content-script fetch in a DNR session rule:
await chrome.declarativeNetRequest.updateSessionRules({ addRules: [rule] });
try { return await fetchViaContentScript(url); }
finally { await chrome.declarativeNetRequest.updateSessionRules({ removeRuleIds: [id] }).catch(warn); }
In Firefox, the MV2 chrome.* namespace is callback-only and returns undefined for these calls. The await on the add call passes, the fetch succeeds, then the finally block does .catch on undefined:
TypeError: can't access property "catch", t.updateSessionRules(...) is undefined
That rejection replaces the successful result. Core maps it to FetcherError, the sign-in handler maps that to bad-rp-id, and the page shim falls back to the browser. The only warning in that path is inside the .catch that never runs.
Verified separately: the content-script fetch returns the document (200, application/json), and browser.declarativeNetRequest.updateSessionRules accepts the same rule.
Repro (temporary add-on via about:debugging)
manifest.json:
{ "manifest_version": 2, "name": "dnr-repro", "version": "0.1",
"permissions": ["<all_urls>", "declarativeNetRequestWithHostAccess"],
"background": { "scripts": ["bg.js"] } }
bg.js:
const rule = { id: 1, priority: 1,
action: { type: "modifyHeaders", requestHeaders: [{ header: "sec-fetch-site", operation: "set", value: "none" }] },
condition: { requestDomains: ["auth.example.com"], urlFilter: "/.well-known/webauthn", resourceTypes: ["xmlhttprequest"] } };
console.log(typeof chrome.declarativeNetRequest.updateSessionRules({ addRules: [rule] }));
// Firefox: "undefined" Chrome: "object"
browser.declarativeNetRequest.updateSessionRules({ removeRuleIds: [1] })
.then(() => console.log("browser.* returns a promise and accepts the rule"));
Fix
Use browser.declarativeNetRequest (or the polyfill) in the fetcher, or guard the return value before calling .catch.
Impact
Every related-origin site, get and create, Firefox only. Because the fallback is silent, users read it as "1Password did not show up" and blame the site or Firefox.
