message-policy.ts

7.4 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  GET_SYNC_ALERTS: "extension-page-only",21  DISMISS_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  EXPORT_ENTRIES: "extension-page-only",34  BATCH_ADD_ENTRIES: "extension-page-only",35  RESTORE_ENTRIES: "extension-page-only",36  CLEAR_VAULT_ENTRIES: "extension-page-only",37  DELETE_ENTRY: "extension-page-only",38  CREATE_VAULT: "extension-page-only",39  CREATE_VAULT_FROM_ROOT_KEY: "extension-page-only",40  CHECK_ALL_BREACHES: "extension-page-only",41  UPDATE_SETTINGS: "extension-page-only",42  SETUP_PIN: "extension-page-only",43  DISABLE_PIN: "extension-page-only",44  ACTIVATE_LICENSE: "extension-page-only",45  GET_LICENSE: "extension-page-only",46  GET_LICENSE_KEY: "extension-page-only",47  IS_DURESS_ENABLED: "extension-page-only",48  SETUP_DURESS: "extension-page-only",49  DISABLE_DURESS: "extension-page-only",50  VERIFY_MAIN_PASSWORD: "extension-page-only",51  CHANGE_PASSWORD: "extension-page-only",52  GET_LOCAL_IDENTITY: "extension-page-only",53  LIST_PAIRED_DEVICES: "extension-page-only",54  RENAME_PAIRED_DEVICE: "extension-page-only",55  UNPAIR_DEVICE: "extension-page-only",56  RENAME_LOCAL_DEVICE: "extension-page-only",57  START_ADOPTION_SOURCE: "extension-page-only",58  START_ADOPTION_RECEIVER: "extension-page-only",59  LIST_PENDING_ADOPTION_HOLDERS: "extension-page-only",60  REMOVE_PENDING_ADOPTION_HOLDER: "extension-page-only",61  CANCEL_ADOPTION_SOURCE: "extension-page-only",62  SOURCE_SAS_DECISION: "extension-page-only",63  RECEIVER_SAS_DECISION: "extension-page-only",64  TRIGGER_SYNC: "extension-page-only",65  GET_SYNC_STATE: "extension-page-only",66  GET_SYNC_GENERATION: "extension-page-only",67  SECURE_RESET: "extension-page-only",68  GET_SYNC_SETTINGS: "extension-page-only",69  SET_SYNC_SETTINGS: "extension-page-only",70  GET_PENDING_PASSKEY: "extension-page-only",71  APPROVE_PASSKEY: "extension-page-only",72  DENY_PASSKEY: "extension-page-only",73  GET_PASSKEY_PROVIDER_STATE: "extension-page-only",74  SET_PASSKEY_PROVIDER_ENABLED: "extension-page-only",75  GET_DELETED_ENTRIES: "extension-page-only",76  RESTORE_ENTRY: "extension-page-only",77  PURGE_ENTRY: "extension-page-only",78  PURGE_EXPIRED_TRASH: "extension-page-only",79  GET_VAULT_HISTORY: "extension-page-only",80  RESTORE_VAULT_SNAPSHOT: "extension-page-only",81  COUNT_DUPLICATES: "extension-page-only",82  DEDUPLICATE_ENTRIES: "extension-page-only",83  GET_VAULT_STATUS: "extension-page-only",84  GET_ADVANCED_HEALTH_REPORT: "extension-page-only",85  DEACTIVATE_LICENSE: "extension-page-only",86  GET_IDENTITIES: "extension-page-only",87  DESTROY_VAULT: "extension-page-only",88  COMPLETE_FIREFOX_PASSKEY_CREATE: "extension-page-only",89  VALIDATE_RECOVERY_WORDS: "extension-page-only",90  RECOVER_WITH_WORDS: "extension-page-only",91  GET_RECOVERY_WORDS: "extension-page-only",92  GET_RECOVERY_CONFIRM_PENDING: "extension-page-only",93  SET_RECOVERY_CONFIRM_PENDING: "extension-page-only",94  CLEAR_RECOVERY_CONFIRM_PENDING: "extension-page-only",95  GET_IDENTITY_SUGGESTIONS: "extension-page-only",96  SAVE_NOTE: "extension-page-only",97  DELETE_NOTE: "extension-page-only",98  GET_NOTES: "extension-page-only",99  SEARCH_VAULT: "extension-page-only",100  GET_CODEX_VAULT_DATA: "extension-page-only",101  ESTIMATE_CODEX_PAGES: "extension-page-only",102  GENERATE_CODEX: "extension-page-only",103  RESTORE_FROM_CODEX: "extension-page-only",104  OPEN_DETACHED_POPUP: "extension-page-only",105106  // Content-script requests that are intentionally narrow or origin-bound in107  // their handlers. These are not bulk vault export or device-authority APIs.108  ADD_ENTRY: "content-script-allowed",109  UPDATE_ENTRY: "content-script-allowed",110  ADD_ADDITIONAL_URL: "content-script-allowed",111  REQUEST_REMEMBER_SITE: "content-script-allowed",112  AUTOFILL_QUERY: "content-script-allowed",113  COPY: "content-script-allowed",114  FIRE_CANARY_PING: "content-script-allowed",115  GET_ENTRY_FOR_FILL: "content-script-allowed",116  GET_ENTRY_FOR_EDIT: "extension-page-only",117  GET_SETTINGS: "content-script-allowed",118  GET_SITE_PASSWORD_POLICY: "content-script-allowed",119  RECORD_MANUAL_SUMMON: "content-script-allowed",120  DECLINE_CARD_SAVE: "content-script-allowed",121  IS_CARD_DECLINED: "content-script-allowed",122  PASSKEY_AUTH_RESPONSE: "content-script-allowed",123  ML_INIT: "content-script-allowed",124  ML_PREDICT_BATCH: "content-script-allowed",125  OPEN_PRO_GATE: "content-script-allowed",126  OPEN_POPUP: "content-script-allowed",127  TOUCH_AUTO_LOCK: "extension-page-only",128  MARK_PASSKEY_BANNER_SHOWN: "content-script-allowed",129130  // These seven are the explicit Firefox MAIN-world passkey relay surface.131  INITIATE_FIREFOX_PASSKEY_CREATE: "page-relay",132  PASSKEY_OFFER_DECLINE: "page-relay",133  GET_PASSKEY_OFFER_DECLINED: "page-relay",134  GET_PASSKEY_FOR_DOMAIN: "page-relay",135  PASSKEY_HAS_CREDENTIALS: "page-relay",136  PASSKEY_CREATE: "page-relay",137  PASSKEY_GET: "page-relay",138139  // Messages produced by the background or handled outside messages.ts.140  CLEAR_CLIPBOARD: "background-internal",141  VOUCHED_DEVICE_PENDING: "background-internal",142  DEVICE_KEY_ROTATED: "background-internal",143  ADOPTION_SOURCE_SAS: "background-internal",144  ADOPTION_SOURCE_RESULT: "background-internal",145  PING: "background-internal",146  SUMMON_AUTOFILL: "background-internal",147  VAULT_LOCKED: "background-internal",148  VAULT_UNLOCKED: "background-internal",149  VAULT_ENTRIES_CHANGED: "background-internal",150  PRO_GATE_TRIGGERED: "background-internal",151  PASSKEY_PRO_GATE: "background-internal",152  SHOW_PASSKEY_TOAST: "background-internal",153  DISMISS_PASSKEY_TOAST: "background-internal",154  SHOULD_PLAY_CELEBRATION: "extension-page-only",155  MARK_CELEBRATION_PLAYED: "extension-page-only",156} satisfies Record<MessageType, MessageContext>;157158export function getMessageContext(type: unknown): MessageContext | undefined {159  if (typeof type !== "string") return undefined;160  return Object.hasOwn(MESSAGE_CONTEXT_POLICY, type)161    ? MESSAGE_CONTEXT_POLICY[type as MessageType]162    : undefined;163}164165export function isMessageSenderAllowed(166  type: unknown,167  sender: MessageSenderShape,168  extensionId: string | undefined,169  extensionOrigin: string,170): boolean {171  if (sender.id !== undefined && sender.id !== extensionId) return false;172173  const context = getMessageContext(type);174  if (context === "background-internal" || context === undefined) return false;175176  if (context === "extension-page-only") {177    if (!sender.url) return false;178    try {179      return new URL(sender.url).origin === new URL(extensionOrigin).origin;180    } catch {181      return false;182    }183  }184185  if (context === "page-relay") {186    if (!sender.url) return false;187    try {188      return new URL(sender.url).origin !== new URL(extensionOrigin).origin;189    } catch {190      return false;191    }192  }193194  return true;195}