ext: pairing-restart test + AMO permission justification

tests/transport/storage.test.ts covers the storage half of "the pairing
token survives a browser restart" (round-trip, unpair-clears, corrupted
override falls back to auto). websocket.test.ts adds the transport half:
a fresh WebSocketTransport instance over the same backing store reuses
the persisted token with no re-pairing, plus pairWithCode/unpair
coverage. "Wrong token rejected and rate-limited" was already covered
(websocket.test.ts's NotPaired/RateLimited cases).

docs/amo-permissions.md is the submission-ready permission justification
for AMO's Notes to Reviewer field, covering every permission in
manifest.json plus what was deliberately not requested and how cookie/
header data is handled.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ed8KEmAW48v4YHdxLtqsMB
This commit is contained in:
2026-09-11 12:25:42 +04:00
co-authored by Claude Sonnet 5
parent 2cb1959bff
commit 25c171f742
5 changed files with 190 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
# AMO permission justification
Submitted with every version bump in the AMO "Notes to Reviewer" field. Kept here so the
justification is reviewed and versioned alongside the permission list itself
(`manifest.json`), instead of living only in a web form. docs/05 §7 is the design-time
version of this; this file is the submission-ready copy.
## What Velox is
A download manager. It intercepts a response Firefox is about to download, hands the
URL, request headers, and cookies to a companion native application (`veloxd`), and lets
that application fetch the file with resumable, multi-connection transfers. The
extension itself never stores or transfers file bytes — see `CLAUDE.md` §3 and the
`no-download-logic` ESLint gate (`eslint.config.mjs`) that fails CI if it ever does.
## Permissions requested
| Permission | Why | Narrowest alternative considered |
|---|---|---|
| `webRequest` + `webRequestBlocking` | The whole feature: inspect response headers on `onHeadersReceived` to decide whether to intercept a download, and `{cancel: true}` before Firefox starts its own download. This is the one thing MV3 Chrome removed and MV3 Firefox kept — it's why the extension can exist as designed (docs/05 §1). | None. Without blocking `webRequest` there is no way to stop Firefox's own download before it starts; polling `downloads.onCreated` alone (which we also use, see below) only catches what already started. |
| `downloads` | Belt-and-braces safety net (`capture/downloads-api.ts`): some downloads (form POSTs, service-worker blobs) never reach `onHeadersReceived` in a way we can act on and only surface via `downloads.onCreated`. Also used to `cancel`/`erase` a download we're taking over so Firefox doesn't keep two copies. | Drop the safety net and accept that those cases silently bypass Velox. Rejected — docs/05 §2 calls this out explicitly as a known gap the safety net exists to close. |
| `cookies` | A file behind a login (private CDN links, forum attachments) needs its session cookies handed to `veloxd`, or the daemon's fetch gets a 403 the browser's own request wouldn't have. Read via `cookies.getAll(url)` only for a URL we are about to offer to the daemon — never harvested in bulk or logged. | Skip cookies and only support anonymous URLs. Rejected — it's a top user-facing IDM-parity feature and the reason people leave Chrome download managers behind. |
| `contextMenus` | "Download with Velox" on a link/image/video, and "Download all links…" (docs/05 §3). Table-stakes UI for a download manager extension. | None smaller — there's no partial grant for context menus. |
| `storage` | `browser.storage.local` holds only extension-local state: the WebSocket pairing token, the manual transport override, the last-good WS port, and the user's default-category preference (`transport/storage.ts`, `options/prefs.ts`). No browsing data. | None — some persistence is required for pairing to survive a restart (M1 DoD), which is the point of the token existing at all. |
| `notifications` | Tells the user when the daemon can't be reached for a download that fell back to Firefox, and (native-messaging path) surfaces pairing prompts if the GUI isn't running. | Silent failure. Rejected — capture fails open by design (CLAUDE.md §4) and a silent fallback with no notification would look like a bug. |
| `nativeMessaging` | Opportunistic transport to `veloxd` over a Unix socket, for installs where it works (docs/adr/0003). Not the default path — WebSocket is — but shipped because it avoids the WebSocket port-scan on installs where the native host manifest is reachable. | Drop native messaging and use WebSocket exclusively. Considered and rejected in ADR 0003: keeping both means the extension keeps working across deb/snap/flatpak Firefox without per-flavour capture-logic forks. |
| `<all_urls>` (host permission) | Downloads happen from every site on the web; `webRequest`'s header inspection and `cookies.getAll` both need to run against whatever site the user is on. This is the item AMO reviewers push back on hardest for extensions of this shape. | A fixed list of "known download sites" — unworkable for a general-purpose download manager, and defeats the point of an IDM-style interceptor. **Mitigation, not a narrower permission:** the exclusion list in Options is front-and-center (`options.html` → "Capture policy") so a user can scope capture down to nothing on sites they don't want Velox touching, and the bypass modifier (default Alt) lets a single click skip capture without changing settings. |
## What is explicitly *not* requested
- No `<all_urls>` XHR/fetch use — `webRequest`/`cookies` read metadata about a request
Firefox is already making; the extension never issues its own network request for
file bytes (enforced by the ESLint gate above).
- No `identity`, `history`, `bookmarks`, `tabs` beyond what `contextMenus`/`commands`
already imply, `management`, or any permission unrelated to capturing and handing off
a download.
- No remote code: the manifest ships no CDN scripts and no `eval`; `web-ext lint` fails
the build otherwise (CI's `extension-lint` job).
## Data handling
- Cookies and headers are held in memory only long enough to answer one
`capture.offer` call to the local daemon (`capture/headers.ts`'s ring buffer, 5-minute
TTL) — never written to disk by the extension and never sent anywhere but
`127.0.0.1`.
- The daemon connection is local-only: `WebSocketTransport` connects to
`ws://127.0.0.1:<port>`, never a remote host (docs/05 §4, conformance-tested).
- Nothing is sent to Anthropic, Mozilla, or any third party beyond the user's own local
`veloxd` process.