Browser-extension + bridge standards — MV3 (Chrome + Firefox), least-privilege permissions, the loopback pairing-token auth model, the shared TS↔Rust wire protocol, and the Chrome Web Store + Firefox AMO store-policy + pre-submission checklist. Load for changes under apps/extension/**, extension_bridge/, and the extension protocol.
Installation
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Browser-extension + bridge standards — MV3 (Chrome + Firefox), least-privilege permissions, the loopback pairing-token auth model, the shared TS↔Rust wire protocol, and the Chrome Web Store + Firefox AMO store-policy + pre-submission checklist. Load for changes under apps/extension/**, extension_bridge/, and the extension protocol.
Extension + bridge standards
Standards for the browser extension and the desktop⇄extension bridge. Load with author-contract (authors) / token-efficiency (reviewers).
Architecture & paths
Extension (apps/extension/**) — MV3, published on Chrome Web Store and Firefox AMO. Content scripts on supported job boards → import the user-selected posting into the desktop app.
Bridge — the extension reaches the desktop app over a loopback-only WebSocket (127.0.0.1, a fixed port range) and Chrome Native Messaging (the native host relays frames verbatim). Server: apps/desktop/src-tauri/src/extension_bridge/** + commands/extension_bridge.rs.
Wire contract — packages/shared/src/ipc/extension-protocol-constants.ts (envelope + EXTENSION_MESSAGE_TYPES), its zod schema extension-protocol.ts, and the Rust msg constants in the bridge. A Rust↔TS parity test pins the message-type strings together.
Auth model (the security boundary)
The per-frame 256-bit pairing token (loopback only) is what authenticates. The origin allowlist (Chrome id / Firefox UUID-shape / native-host sentinel) is defense-in-depth only.
A connection is authorized/connected ONLY after a frame passes the token check — never on the WS handshake alone. On open the extension sends { type: "auth", token, reqId, payload: null }; the desktop replies an import.result with no error (= authorized) or { error: "unauthorized" }and closes the socket. A wrong token is never reported as connected. (This is the fix for the "any token looks authorized" bug — don't regress it.)
Never echo/confirm the token back; never log it. Token file is owner-only (0o600) on unix.
Protocol lockstep (don't let the two sides drift)
A new message type/field is added in the same change to: the TS EXTENSION_MESSAGE_TYPES constant, the TS zod ExtensionMessageTypeSchema, and the Rust msg module. The envelope shape (type / token / reqId / payload) stays identical on both sides. The parity + uniqueness tests must stay green.
Browser-coverage lockstep — when browser detection adds a browser (a new Chromium-family or Flatpak id), add its native-messaging host manifest entry in the same change (native + the per-app Flatpak dir); a detected browser with no manifest can't pair (#486 detected Vivaldi but never registered it). Grep the sibling manifest table for every id the detector can return.
Rust + tests — the bridge/detection Rust (extension_bridge/**, platform/chrome/**) and its tests also obey rust-standards + testing-rules (cfg-gated/cross-OS, bounded external processes, env-#[serial], no host-coupled/exec()-in-test), not just extension-standards.
Manifest V3 rules (both stores)
No remote code (absolute) — all JS bundled in the package; no eval, no external <script>, no runtime code fetch. Fetching data (JSON) is fine; executing fetched code is not. (CWS MV3, AMO policies)
CSP at/above the MV3 default (script-src 'self' 'wasm-unsafe-eval'; object-src 'self').
Service worker (Chrome) is event-driven; native messaging is a sanctioned reason it may outlive the idle limit. Firefox uses non-persistent background scripts/event pages (no persistent background in MV3).
web_accessible_resources — expose the minimum, scoped to the specific board origins; prefer use_dynamic_url to reduce fingerprinting.
Firefox needs browser_specific_settings.gecko.id for MV3 (Chrome ignores the key — keep one shared manifest).
Permissions — least privilege
Request only what an existing feature needs; no future-proofing (requesting a permission for an unbuilt feature is a rejection trigger). (CWS permissions)
Scope host_permissions to the exact supported board origins — never <all_urls>. Prefer activeTab / optional permissions where they suffice.
nativeMessaging only; native/WS calls run in the SW/extension pages, not content scripts.
Single purpose (Chrome): one narrow, easily-understood purpose; don't bundle unrelated features.
Store policy + pre-submission checklist
Date-sensitive (re-verify the two source pages before each submission):
CWS Program Policies last updated 2025-05-22; the 2025 wave added one-appeal-per-violation, a real-money-gambling ban, and single-purpose clarifications. (2025 blog)
Firefox: in H1 2026, Mozilla requires ALL extensions (incl. existing ones, on their next update) to adopt the built-in data-collection consent framework. New extensions already required since 2025-11-03. This extension is already published → treat as in-scope now. (Firefox data consent, Mozilla blog)
MV2 is end-of-life — author/review against MV3 only.
Run this before any release (both stores):
host_permissions scoped to exact board origins; no <all_urls>, no unused/future-proof perms; nativeMessaging declared.
Data leaving the browser (bridge/native host) is limited to the user-acted-on posting + the pairing handshake — no persistent IDs, analytics, cookies, ad data; never shared with third parties. (CWS limited use, AMO)
Firefox: browser_specific_settings.gecko.id set; gecko.data_collection_permissions (required/optional, or ["none"]) declared accurately (technicalAndInteraction can only be optional); consent UI present (own or built-in); privacy-policy link recommended.
Firefox source submission (build is bundled/minified → required): full source + README build steps + lockfile; build reproduces and diffs clean against the package; no obfuscation. (source submission)
Listing honest: real screenshots, icon, accurate single-purpose description, no keyword stuffing (<5/keyword).
Common rejections
Chrome: over-broad/unjustified host_permissions; any remote code; single-purpose violation; missing icon/screenshot/privacy-policy; incomplete data-usage tab; collecting data without in-UI disclosure+consent.
Firefox: bundled code without reproducible source + lockfile; unnecessary permissions; undisclosed data transmission ("No Surprises"); leaking local/user data via native messaging; missing data-collection consent.