Skip to content

Phase 4 §7.1: bundle uBlock Origin in profile template#748

Merged
bicced merged 4 commits intomainfrom
feat/phase4-ublock
Apr 25, 2026
Merged

Phase 4 §7.1: bundle uBlock Origin in profile template#748
bicced merged 4 commits intomainfrom
feat/phase4-ublock

Conversation

@bicced
Copy link
Copy Markdown
Contributor

@bicced bicced commented Apr 25, 2026

Summary

§7.1 of the browser-automation roadmap — bundles uBlock Origin's signed XPI in the browser image and installs it into every agent profile via the profile-schema framework.

  • XPI bundled at build time. Dockerfile.browser downloads uBlock0_${UBLOCK_VERSION}.firefox.signed.xpi from the official GitHub releases (pinned via UBLOCK_VERSION build arg, defaults to 1.62.0). Stored read-only at /opt/openlegion/extensions/uBlock0.xpi.
  • Profile schema v3 migration (_v3_install_ublock) drops the XPI into {profile}/extensions/{addon-id}.xpi where Firefox auto-discovers it on launch. Idempotent — size+mtime guard skips recopy on unchanged XPI; refreshes when the image rebuilds with a newer version.
  • Cooperating stealth prefs in _stealth_prefs(): extensions.autoDisableScopes=0, extensions.startupScanScopes=15, xpinstall.signatures.required=False. Without these Firefox sees the file but installs disabled.
  • Operator escape hatch. BROWSER_ENABLE_ADBLOCK=false skips install at migration time and at every-launch sync time. Default stays true per §2.1.
  • Launch-time sync helper sync_adblock_extension() runs at every BrowserManager.get_or_start so flag toggles after a profile is at v3 (or image rebuilds with a newer XPI) take effect without another schema bump.

Risk and rollout

  • Default-true after this ships, per §2.1. uBlock Origin's defaults (EasyList + EasyPrivacy) are well-tested and won't break authentication flows or e-commerce checkouts. The roadmap's known-risk site (Forbes-class adblock walls) is covered by the operator kill switch — explicit per-host opt-out is deferred until we see real failures, since the spec is ambiguous between "operator config endpoint" and "per-page runtime disable" and the latter requires fragile uBO storage poking.
  • Migration is wrapped in the existing backup-restore framework: a partial v3 failure restores the pre-migration profile.
  • Source-XPI-missing path is graceful: dev installs whose Dockerfile.browser hasn't been rebuilt log INFO and continue, letting the next launch on a properly-built image fix the install via sync_adblock_extension.

Test plan

  • Migration copies XPI to {profile}/extensions/{addon-id}.xpi with mode 0644.
  • Source missing → migration logs and skips, profile still stamps v3.
  • BROWSER_ENABLE_ADBLOCK=false → install skipped.
  • Re-running migration on already-installed profile is a no-op (no recopy).
  • Source XPI changes (size + mtime) → migration recopies.
  • End-to-end v0 → v3 runs v2 (font cache wipe) then v3 (XPI install) in order.
  • v3 preserves cookies, prefs, and IndexedDB (cookies.sqlite, prefs.js, storage/default).
  • sync_adblock_extension returns True when installed, False on flag-disabled / source-missing / failure.
  • Sync helper never raises (best-effort — must not block browser launch).
  • Stealth prefs include autoDisableScopes=0, startupScanScopes=15, signatures.required=False.
  • 41/41 in test_profile_schema.py; 308/308 across test_browser_service.py + test_stealth.py (no regressions).

bicced added 4 commits April 25, 2026 20:24
Bundles uBlock Origin's signed XPI in Dockerfile.browser
(/opt/openlegion/extensions/uBlock0.xpi, version pinned via
UBLOCK_VERSION arg). Profile schema bumps to v3 with a migration
that copies the bundled XPI into each profile's extensions/{addon-id}.xpi
where Firefox auto-discovers it on launch.

Stealth prefs gain three Firefox prefs that cooperate with the
profile-bundled extension: extensions.autoDisableScopes=0,
extensions.startupScanScopes=15, and xpinstall.signatures.required=False.

Operator escape hatch via BROWSER_ENABLE_ADBLOCK=false skips the
install at migration time and at every-launch sync time. The flag
default stays "true" per §2.1 — uBlock Origin's default filter lists
(EasyList + EasyPrivacy) ship enabled.

Migration is idempotent: size+mtime guard skips recopy on unchanged
XPI, refreshes when the operator rebuilds the image with a newer
version. The launch-time sync_adblock_extension() helper runs every
browser start so flag toggles after a profile is at v3 take effect
without needing another schema bump.

Per-host opt-out is deferred to a follow-up — the ambiguity in §7.1
between "operator endpoint" and per-page disable means it deserves
its own design pass once we have a concrete site-detection report
(e.g. Forbes-style adblock-walled sites in production).
sync_adblock_extension now acquires the same per-profile flock that
migrate_profile uses, so two browser-service processes (e.g. a
provisioner update path racing an agent restart) can't both write to
{profile}/extensions/ at once. Non-blocking — if the peer holds the
lock, we skip and report present-or-not based on a stat. Idempotency
of _v3_install_ublock means the eventual disk state is correct
regardless of who wins the race.
…tests

- XPI download moved BEFORE pyproject.toml COPY so Python-dependency
  bumps no longer invalidate the 3MB XPI download cache.
- URL fallback to .firefox.xpi (unsigned) when .firefox.signed.xpi
  404s. Lets developers pin to a uBO beta release that hasn't been
  AMO-signed yet without hand-editing the URL — Camoufox's patched
  Firefox accepts unsigned XPIs anyway because
  xpinstall.signatures.required=False.
- Two new tests for sync_adblock_extension flock contention:
  (a) peer holds lock + existing XPI present → returns True without
      re-installing
  (b) peer holds lock + no XPI yet → returns False (peer hasn't
      finished install)
@bicced bicced merged commit ee95754 into main Apr 25, 2026
3 of 4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant