extension-standards
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_bri
Install
mkdir -p .claude/skills/extension-standards && curl -L -o skill.zip "https://agentskills.codes/api/skills/download/14715" && unzip -o skill.zip -d .claude/skills/extension-standards && rm skill.zipInstalls to .claude/skills/extension-standards
Activation
This is the description your AI agent reads to decide when to run this skill — the better it matches your request, the more reliably it fires.
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.About this skill
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 schemaextension-protocol.ts, and the Rustmsgconstants 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 animport.resultwith noerror(= 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; preferuse_dynamic_urlto reduce fingerprinting.- Firefox needs
browser_specific_settings.gecko.idfor 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_permissionsto the exact supported board origins — never<all_urls>. PreferactiveTab/ optional permissions where they suffice. nativeMessagingonly; 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_permissionsscoped to exact board origins; no<all_urls>, no unused/future-proof perms;nativeMessagingdeclared. - Zero remote code; CSP at/above MV3 default;
web_accessible_resourcesminimal + origin-scoped. - 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)
- Chrome: privacy-policy URL set; Privacy-practices/Data-usage disclosures + Limited-Use certification complete; in-product prominent disclosure + consent before collection; the pairing token + scraped posting both disclosed; HTTPS/secure transport. (CWS privacy, dashboard privacy)
- Firefox:
browser_specific_settings.gecko.idset;gecko.data_collection_permissions(required/optional, or["none"]) declared accurately (technicalAndInteractioncan 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.