Moat  ·  ad blocker & popup / redirect firewall

A quiet ad blocker that keeps to itself.

Moat blocks ads, trackers, and hijacked popups on Chrome and Firefox — and then does nothing else. No server. No account. No telemetry. No crash reporting. Everything it does runs on your device, against pages already in your browser. Its developer receives nothing about you, under any setting, ever. Built for Manifest V3 from day one, not a stripped-down port of an older extension.

Status: active development — install unpacked from source today; Chrome Web Store and Firefox Add-ons submissions are part of the release process, not yet live.

0
servers the developer operates
0
telemetry / analytics events
271,274
bundled declarativeNetRequest rules
GPL-3.0
source, auditable end to end
In four lines

What Moat reads, and where it goes

Stays on your device

  • Page content on sites you visit — read locally to block ads, trackers, and popups and to hide ad elements. Never sent anywhere.
  • Your settings (filter lists, paused sites, custom block/allow and cosmetic rules) live in your browser's own local storage.
  • The block counts in the toolbar badge are computed locally from the browser's own match data.
  • A rolling 14-day usage history (what got blocked, per-site and per-setting) backs the Settings page's own charts — kept in local storage only, never part of the settings sync payload, and excluded from settings export/import.

Leaves your device

  • Roughly once a day: a few small static filter-fix files downloaded from a public GitHub Pages URL. A plain file fetch — it carries nothing about you beyond the IP any HTTP request shows.
  • Two opt-in features, off by default: a leaked-password check (a 5-char hash prefix to Have I Been Pwned) and tracker CNAME-uncloaking (a hostname to a public DNS resolver, on Chrome only). Third parties, never Moat.
  • One enterprise-only integration: minimized security events to your own organization's server, only if IT policy provisions it. Impossible to enable as a regular user.

That is the complete list. The full detail, case by case, is in PRIVACY.md.

Enforce

What happens on every page load

runs: every navigation

Three enforcement layers act in parallel, all locally: the declarative network filter, the page-side content scripts, and a tab-level safety net for popups the scripts never saw.

Network request Any subresource, frame, or navigation the page makes.
Matches a static DNR rule?
matchBlock or redirect to a neutered stub resource.
no matchRequest proceeds normally.
Content scripts @ document_start Inject cosmetic CSS (## selectors), scriptlets, and the fingerprint guard in the MAIN world.
New tab lands on a known redirect / popup domain?
matchClose the tab silently. Baseline list + the live slice.
no matchWatched briefly for a late script-driven redirect, then released.
Refresh

The live-update channel

runs: every ~24 h

A deliberately narrow slice — ~460 popup domains, an emergency block/allow list, and cosmetic-selector fixes — kept fresher than the store-release cadence. The manifest is verified against a bundled Ed25519 public key, and every payload against a SHA-256 in that manifest, before anything is applied; a failed check keeps the bundled baseline. Hosted on GitHub Pages (10-minute cache), so the host holds no trust of its own.

Alarm fires Periodic ~24 h, create-if-absent so a service-worker restart can't re-arm it early. First fire jittered 30–180 min.
Last fetch OK < 18 h ago?
freshSkip the network. popupGuard re-hydrates its live set from storage.local.
staleFetch manifest.json + .sig from GitHub Pages.
Signature + per-payload SHA-256 verify?
failsKeep the bundled baseline. Status = failed, retry next tick.
verifiesApply the three payloads — see below.
On a verified apply, the three payloads feed straight into enforcement:
redirect-domains → dynamic block DNR rules (persistent) + popupGuard's live set, also persisted for cold starts.
quick-fixes → dynamic block / allow rules only. No redirect / modifyHeaders — those "unsafe" types can't come from a remote source.
cosmetic-fixes → validated CSS selectors written to storage.local, applied by the content script on the next page load.

Why the privacy claim holds

  • Nothing to sell. No telemetry, no account, no server — an ownership change would have little to monetise.
  • The remote channel carries data, not code. Only block/allow rules and CSS selectors, each run through the same isSafeCosmeticSelector sink as your own rules, with length and count caps.
  • Signed, then hashed. The manifest is checked against a bundled Ed25519 key; each payload against a SHA-256 in the manifest. The shape validators bound even a fully compromised source to "block/allow a domain set".
  • Explicit CSP, all scripts same-origin, no eval, no remote script. The dashboard renders via createElement + textContent only.
  • <all_urls> at install — standard for a content blocker; there is no other way to inspect requests on every site.

Why it holds at scale

  • No shared bottleneck. Every install runs independently; the browser stores absorb distribution and auto-update. 10k or 10M users is Google / Mozilla / GitHub's load, not a Moat server's.
  • The only recurring outbound call for a consumer install is the daily GitHub Pages pull — a few KB, ~1–2×/day per user, behind Cloudflare's cache.
  • Chrome's static-rule budget is shared across every installed extension. Moat reacts to budget pressure and offers a smaller Lite preset; the ceiling can't be raised from inside an extension.