passkeys-firefox-relay.test.ts

2.6 KB · raw

1// @vitest-environment jsdom23import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";45const mockSendMessage = vi.fn().mockResolvedValue({ ok: true });67vi.stubGlobal("chrome", {8  runtime: {9    id: "ext-id",10    sendMessage: mockSendMessage,11  },12});1314beforeAll(async () => {15  await import("./passkeys-firefox-relay");16});1718async function fire(payload: unknown, source: Window | null = window) {19  window.dispatchEvent(20    new MessageEvent("message", {21      data: { type: "PEACH_PASSKEY_REQUEST", id: "x", payload },22      source,23    })24  );25  await new Promise((resolve) => setTimeout(resolve, 10));26}2728describe("Firefox passkey relay allowlist", () => {29  beforeEach(() => {30    mockSendMessage.mockClear();31  });3233  const allowed = [34    "PASSKEY_CREATE",35    "PASSKEY_GET",36    "PASSKEY_HAS_CREDENTIALS",37    "INITIATE_FIREFOX_PASSKEY_CREATE",38    "PASSKEY_OFFER_DECLINE",39    "GET_PASSKEY_OFFER_DECLINED",40    "GET_PASSKEY_FOR_DOMAIN",41    "PASSKEY_AUTH_RESPONSE",42  ];4344  const blocked = [45    // Popup-only: never page-relayable (Codex Security 2026-08-04).46    "COMPLETE_FIREFOX_PASSKEY_CREATE",47    "GET_ENTRIES",48    "GET_RECOVERY_WORDS",49    "DESTROY_VAULT",50    "GET_DERIVED_KEY",51    "ADD_ENTRY",52    "UPDATE_ENTRY",53    "DELETE_ENTRY",54    "CLEAR_VAULT_ENTRIES",55    "RESTORE_FROM_CODEX",56    "CHANGE_PASSWORD",57    "RECOVER_WITH_WORDS",58    "SETUP_DURESS",59    "PAIRING_COMPLETED",60    "ACTIVATE_LICENSE",61    "TRIGGER_SYNC",62    "GET_IDENTITIES",63    "SEARCH_VAULT",64    "GET_ENTRY_FOR_FILL",65    "LOCAL_WEBSOCKET_PROBE",66    "SUBNET_DETECT_PROBE",67    "",68    "TOTALLY_MADE_UP",69  ];7071  for (const type of allowed) {72    it(`forwards allowed message: ${type}`, async () => {73      await fire({ type });74      expect(mockSendMessage).toHaveBeenCalledWith({ type });75    });76  }7778  for (const type of blocked) {79    it(`blocks message: ${type || "(empty)"}`, async () => {80      await fire({ type });81      expect(mockSendMessage).not.toHaveBeenCalled();82    });83  }8485  it("blocks null payload", async () => {86    await fire(null);87    expect(mockSendMessage).not.toHaveBeenCalled();88  });8990  it("blocks empty payload object", async () => {91    await fire({});92    expect(mockSendMessage).not.toHaveBeenCalled();93  });9495  it("blocks cross-frame messages", async () => {96    await fire({ type: "PASSKEY_GET" }, null);97    expect(mockSendMessage).not.toHaveBeenCalled();98  });99100  it("does not post a response for blocked messages", async () => {101    const spy = vi.spyOn(window, "postMessage");102    await fire({ type: "GET_ENTRIES" });103    expect(spy).not.toHaveBeenCalled();104    spy.mockRestore();105  });106});