message-policy.ts

7.1 KB · raw

1import type { MessageType } from "../shared/types";23export type MessageContext =4  | "extension-page-only"5  | "content-script-allowed"6  | "background-internal"7  | "page-relay";89export interface MessageSenderShape {10  id?: string;11  url?: string;12}1314/**15 * Every message type has an explicit trust boundary. Keep this record16 * exhaustive: adding a MessageType without choosing its sender context must17 * fail type-checking instead of silently becoming trusted.18 */19export const MESSAGE_CONTEXT_POLICY = {20  // Popup/options operations and sensitive reads/writes.21  GET_SYNC_ALERTS: "extension-page-only",22  GET_PENDING_VOUCHED_DEVICES: "extension-page-only",23  CONFIRM_VOUCHED_DEVICE: "extension-page-only",24  REJECT_VOUCHED_DEVICE: "extension-page-only",25  ROTATE_SYNC_IDENTITY: "extension-page-only",26  UNLOCK: "extension-page-only",27  UNLOCK_WITH_PIN: "extension-page-only",28  GET_PIN_STATUS: "extension-page-only",29  LOCK: "extension-page-only",30  IS_UNLOCKED: "extension-page-only",31  HAS_VAULT: "extension-page-only",32  GET_ENTRIES: "extension-page-only",33  BATCH_ADD_ENTRIES: "extension-page-only",34  RESTORE_ENTRIES: "extension-page-only",35  CLEAR_VAULT_ENTRIES: "extension-page-only",36  DELETE_ENTRY: "extension-page-only",37  CREATE_VAULT: "extension-page-only",38  CREATE_VAULT_FROM_ROOT_KEY: "extension-page-only",39  CHECK_ALL_BREACHES: "extension-page-only",40  UPDATE_SETTINGS: "extension-page-only",41  SETUP_PIN: "extension-page-only",42  DISABLE_PIN: "extension-page-only",43  ACTIVATE_LICENSE: "extension-page-only",44  GET_LICENSE: "extension-page-only",45  GET_LICENSE_KEY: "extension-page-only",46  IS_DURESS_ENABLED: "extension-page-only",47  SETUP_DURESS: "extension-page-only",48  DISABLE_DURESS: "extension-page-only",49  VERIFY_MAIN_PASSWORD: "extension-page-only",50  CHANGE_PASSWORD: "extension-page-only",51  GET_LOCAL_IDENTITY: "extension-page-only",52  LIST_PAIRED_DEVICES: "extension-page-only",53  RENAME_PAIRED_DEVICE: "extension-page-only",54  UNPAIR_DEVICE: "extension-page-only",55  RENAME_LOCAL_DEVICE: "extension-page-only",56  START_ADOPTION_SOURCE: "extension-page-only",57  START_ADOPTION_RECEIVER: "extension-page-only",58  CANCEL_ADOPTION_SOURCE: "extension-page-only",59  SOURCE_SAS_DECISION: "extension-page-only",60  TRIGGER_SYNC: "extension-page-only",61  GET_SYNC_STATE: "extension-page-only",62  GET_SYNC_SETTINGS: "extension-page-only",63  SET_SYNC_SETTINGS: "extension-page-only",64  GET_PENDING_PASSKEY: "extension-page-only",65  APPROVE_PASSKEY: "extension-page-only",66  DENY_PASSKEY: "extension-page-only",67  GET_PASSKEY_PROVIDER_STATE: "extension-page-only",68  SET_PASSKEY_PROVIDER_ENABLED: "extension-page-only",69  GET_DELETED_ENTRIES: "extension-page-only",70  RESTORE_ENTRY: "extension-page-only",71  PURGE_ENTRY: "extension-page-only",72  PURGE_EXPIRED_TRASH: "extension-page-only",73  GET_VAULT_HISTORY: "extension-page-only",74  RESTORE_VAULT_SNAPSHOT: "extension-page-only",75  COUNT_DUPLICATES: "extension-page-only",76  DEDUPLICATE_ENTRIES: "extension-page-only",77  GET_VAULT_STATUS: "extension-page-only",78  GET_ADVANCED_HEALTH_REPORT: "extension-page-only",79  DEACTIVATE_LICENSE: "extension-page-only",80  GET_IDENTITIES: "extension-page-only",81  DESTROY_VAULT: "extension-page-only",82  COMPLETE_FIREFOX_PASSKEY_CREATE: "extension-page-only",83  VALIDATE_RECOVERY_WORDS: "extension-page-only",84  RECOVER_WITH_WORDS: "extension-page-only",85  GET_RECOVERY_WORDS: "extension-page-only",86  GET_RECOVERY_CONFIRM_PENDING: "extension-page-only",87  SET_RECOVERY_CONFIRM_PENDING: "extension-page-only",88  CLEAR_RECOVERY_CONFIRM_PENDING: "extension-page-only",89  GET_IDENTITY_SUGGESTIONS: "extension-page-only",90  SAVE_NOTE: "extension-page-only",91  DELETE_NOTE: "extension-page-only",92  GET_NOTES: "extension-page-only",93  SEARCH_VAULT: "extension-page-only",94  GET_CODEX_VAULT_DATA: "extension-page-only",95  ESTIMATE_CODEX_PAGES: "extension-page-only",96  GENERATE_CODEX: "extension-page-only",97  RESTORE_FROM_CODEX: "extension-page-only",98  OPEN_DETACHED_POPUP: "extension-page-only",99100  // Content-script requests that are intentionally narrow or origin-bound in101  // their handlers. These are not bulk vault export or device-authority APIs.102  ADD_ENTRY: "content-script-allowed",103  UPDATE_ENTRY: "content-script-allowed",104  ADD_ADDITIONAL_URL: "content-script-allowed",105  REQUEST_REMEMBER_SITE: "content-script-allowed",106  AUTOFILL_QUERY: "content-script-allowed",107  COPY: "extension-page-only",108  FIRE_CANARY_PING: "content-script-allowed",109  GET_ENTRY_FOR_FILL: "content-script-allowed",110  GET_ENTRY_FOR_EDIT: "extension-page-only",111  GET_SETTINGS: "content-script-allowed",112  GET_SITE_PASSWORD_POLICY: "content-script-allowed",113  RECORD_MANUAL_SUMMON: "content-script-allowed",114  DECLINE_CARD_SAVE: "content-script-allowed",115  IS_CARD_DECLINED: "content-script-allowed",116  PASSKEY_AUTH_RESPONSE: "content-script-allowed",117  ML_INIT: "content-script-allowed",118  ML_PREDICT_BATCH: "content-script-allowed",119  OPEN_PRO_GATE: "content-script-allowed",120  OPEN_POPUP: "content-script-allowed",121  TOUCH_AUTO_LOCK: "extension-page-only",122  MARK_PASSKEY_BANNER_SHOWN: "content-script-allowed",123124  // These seven are the explicit Firefox MAIN-world passkey relay surface.125  INITIATE_FIREFOX_PASSKEY_CREATE: "page-relay",126  PASSKEY_OFFER_DECLINE: "page-relay",127  GET_PASSKEY_OFFER_DECLINED: "page-relay",128  GET_PASSKEY_FOR_DOMAIN: "page-relay",129  PASSKEY_HAS_CREDENTIALS: "page-relay",130  PASSKEY_CREATE: "page-relay",131  PASSKEY_GET: "page-relay",132133  // Messages produced by the background or handled outside messages.ts.134  CLEAR_CLIPBOARD: "background-internal",135  VOUCHED_DEVICE_PENDING: "background-internal",136  DEVICE_KEY_ROTATED: "background-internal",137  ADOPTION_SOURCE_SAS: "background-internal",138  ADOPTION_SOURCE_RESULT: "background-internal",139  PING: "background-internal",140  SUMMON_AUTOFILL: "background-internal",141  VAULT_LOCKED: "background-internal",142  VAULT_UNLOCKED: "background-internal",143  PRO_GATE_TRIGGERED: "background-internal",144  PASSKEY_PRO_GATE: "background-internal",145  SHOW_PASSKEY_TOAST: "background-internal",146  DISMISS_PASSKEY_TOAST: "background-internal",147  SHOULD_PLAY_CELEBRATION: "extension-page-only",148  MARK_CELEBRATION_PLAYED: "extension-page-only",149} satisfies Record<MessageType, MessageContext>;150151export function getMessageContext(type: unknown): MessageContext | undefined {152  if (typeof type !== "string") return undefined;153  return Object.hasOwn(MESSAGE_CONTEXT_POLICY, type)154    ? MESSAGE_CONTEXT_POLICY[type as MessageType]155    : undefined;156}157158export function isMessageSenderAllowed(159  type: unknown,160  sender: MessageSenderShape,161  extensionId: string | undefined,162  extensionOrigin: string,163): boolean {164  if (sender.id !== undefined && sender.id !== extensionId) return false;165166  const context = getMessageContext(type);167  if (context === "background-internal" || context === undefined) return false;168169  if (context === "extension-page-only") {170    if (!sender.url) return false;171    try {172      return new URL(sender.url).origin === new URL(extensionOrigin).origin;173    } catch {174      return false;175    }176  }177178  if (context === "page-relay") {179    if (!sender.url) return false;180    try {181      return new URL(sender.url).origin !== new URL(extensionOrigin).origin;182    } catch {183      return false;184    }185  }186187  return true;188}