Design note: Firefox Add to Rollup

Status: shipped MVP (unsigned temporary add-on). Ingest-only; not a read-later library. Thunderbird XPI remains a non-goal.

Capture enqueues HTTPS URLs into the existing webpage_queue. The digest still fetches the page later. rollup web must be running.

Reload

There is no signed XPI. The add-on is Load Temporary Add-on only (about:debugging#/runtime/this-firefox → choose extension/firefox/manifest.json).

After Firefox restarts the temporary add-on is gone. Load manifest.json again. Options storage does not survive the unload — paste the capture token from Articles if the options page is empty. rollup web must already be running.

While iterating (Firefox still open): keep This Firefox debugging open and click Reload next to Rollup after editing JS/CSS/HTML. Origin and token in browser.storage.local survive Reload. Changes to manifest.json (permissions, background.scripts, gecko.id) need Remove then Load Temporary Add-on again.

First-load pairing (token copy, Test connection) is in extension/firefox/README.md.

Packages

The add-on has no npm/bundler dependencies. It uses Firefox WebExtensions APIs only (browser.action, browser.menus, browser.notifications, browser.storage, browser.permissions, fetch). Manifest V3, gecko.id addon@rollup.local, strict_min_version 109.

The capture route adds no new Python packages. It uses the existing rollup[web] extra (flask>=3.1.3,<4) plus stdlib (hmac, secrets). Queue validation stays in rollup.webpage.url / rollup.webpage.queue.

SLOC

Snapshot 2026-09-03 (cloc, excluding PNG and Markdown):

Tree

Files

Code

extension/firefox (JS/CSS/HTML/JSON)

5

433

Capture server + tests (articles.py, secrets.py, test_web_articles_capture.py)

3

494

articles.py includes the older HTML form routes (/add, remove, retry), not only capture. Re-count:

cloc --exclude-ext=md,png extension/firefox
cloc src/rollup/web/routes/articles.py src/rollup/web/secrets.py tests/test_web_articles_capture.py

No live Firefox harness; pytest covers the HTTP contract only.

Known issues

  • Unsigned / temporary. Load via about:debugging. The add-on unloads when Firefox restarts — reload. AMO signing is out of scope.

  • Firefox only. No Chrome/Safari build. Manifest uses background.scripts (not Chrome service_worker).

  • rollup web required. There is no nativeMessaging helper and no enqueue path that talks to SQLite directly.

  • Loopback HTTP. Origin must be http://127.0.0.1, http://localhost, or http://[::1]. Custom ports need an extra host permission at save time. http://[::1]/* optional permission support varies by Firefox version.

  • Restricted pages. about:, moz-extension:, file:, blob:, data: URLs are rejected in the add-on before POST.

  • Token on GET /articles. Anyone who can read the loopback HTML can copy the token. Random websites cannot: no CORS. Do not put the token in query strings or TOML.

  • Do not “fix” pairing with CORS or CSRF exemptions. Session cookies are SameSite=Strict, so moz-extension:// cannot use /articles/add. Bearer on /articles/capture is the intended seam.

  • Test connection POSTs an empty url (expects 400 url_invalid if the token is good). It must not enqueue.

  • Notifications require the Firefox permission prompt on first use.

Code reviews

Review this surface as a local capability, not a public API.

Must stay true

  • No Access-Control-Allow-* headers

  • Form POSTs still require session CSRF; capture ignores CSRF and requires Bearer

  • Host header remains loopback-only (headers.trusted_host_allowed)

  • {state_dir}/extension_token is mode 0600, no symlinks, never TOML

  • validate_queue_url + enqueue_url unchanged for SSRF / duplicates

  • Extension sends URL + title only (no HTML body)

Test map (tests/test_web_articles_capture.py): created / duplicate / retried; invalid URL + SSRF; missing/wrong token; CSRF does not authorize capture; form-urlencoded → 415; GET → 405; non-loopback Host → 400; rotate invalidates; token file perms + symlink refuse.

When reviewing a change, prefer those tests over a browser walkthrough. If you change pairing (token header, origin rules, JSON shape), update this note, WEB.md, and extension/firefox/README.md.