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
Installer avec Codex ou Claude Copiez ce prompt, collez-le dans Codex, Claude ou un autre assistant, puis laissez-le vérifier la page du skill et l'installer pour vous.
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.