message-policy.ts

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