// @vitest-environment jsdom import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; const mockSendMessage = vi.fn().mockResolvedValue({ ok: true }); vi.stubGlobal("chrome", { runtime: { id: "ext-id", sendMessage: mockSendMessage, }, }); beforeAll(async () => { await import("./passkeys-firefox-relay"); }); async function fire(payload: unknown, source: Window | null = window) { window.dispatchEvent( new MessageEvent("message", { data: { type: "PEACH_PASSKEY_REQUEST", id: "x", payload }, source, }) ); await new Promise((resolve) => setTimeout(resolve, 10)); } describe("Firefox passkey relay allowlist", () => { beforeEach(() => { mockSendMessage.mockClear(); }); const allowed = [ "PASSKEY_CREATE", "PASSKEY_GET", "PASSKEY_HAS_CREDENTIALS", "INITIATE_FIREFOX_PASSKEY_CREATE", "PASSKEY_OFFER_DECLINE", "GET_PASSKEY_OFFER_DECLINED", "GET_PASSKEY_FOR_DOMAIN", ]; const blocked = [ // Popup-only: never page-relayable (Codex Security 2026-08-04). "COMPLETE_FIREFOX_PASSKEY_CREATE", // Approval originates from the extension toast/popup, never the page. // Relayable requestId brute-force → consent bypass (Audit 2026-08-20 #1). "PASSKEY_AUTH_RESPONSE", "GET_ENTRIES", "GET_RECOVERY_WORDS", "DESTROY_VAULT", "ADD_ENTRY", "UPDATE_ENTRY", "DELETE_ENTRY", "CLEAR_VAULT_ENTRIES", "RESTORE_FROM_CODEX", "CHANGE_PASSWORD", "RECOVER_WITH_WORDS", "SETUP_DURESS", "PAIRING_COMPLETED", "ACTIVATE_LICENSE", "TRIGGER_SYNC", "GET_IDENTITIES", "SEARCH_VAULT", "GET_ENTRY_FOR_FILL", "LOCAL_WEBSOCKET_PROBE", "SUBNET_DETECT_PROBE", "", "TOTALLY_MADE_UP", ]; for (const type of allowed) { it(`forwards allowed message: ${type}`, async () => { await fire({ type }); expect(mockSendMessage).toHaveBeenCalledWith({ type }); }); } for (const type of blocked) { it(`blocks message: ${type || "(empty)"}`, async () => { await fire({ type }); expect(mockSendMessage).not.toHaveBeenCalled(); }); } it("blocks null payload", async () => { await fire(null); expect(mockSendMessage).not.toHaveBeenCalled(); }); it("blocks empty payload object", async () => { await fire({}); expect(mockSendMessage).not.toHaveBeenCalled(); }); it("blocks cross-frame messages", async () => { await fire({ type: "PASSKEY_GET" }, null); expect(mockSendMessage).not.toHaveBeenCalled(); }); it("does not post a response for blocked messages", async () => { const spy = vi.spyOn(window, "postMessage"); await fire({ type: "GET_ENTRIES" }); expect(spy).not.toHaveBeenCalled(); spy.mockRestore(); }); });