rules.muga.app if you enable Remote rule updates in Settings (off by default). No cookies, no identifiers, no POST body. Auditable open-source endpoint.
esbuild, used only to bundle the cleaning library into the content script; output is committed to the repo)
URLs. Cleaned locally inside your browser by the content script's bundled cleaning library. The cleaning runs inline in the page's content script. It does not round-trip through the service worker or any external server. URLs are never stored beyond the current session. Session history (recently cleaned URLs, per-tab badge counts, debug logs) lives in chrome.storage.session and is automatically cleared when the browser restarts.
Behavioural preferences. Stored in chrome.storage.sync: language, blacklist, whitelist, custom tracking parameters, and the default values for the affiliate toggles and remote-rule-updates toggle. Encrypted and managed by your browser, synced to your Google or Firefox account, not to any server we control or can access.
Consent and per-device decisions. Stored in chrome.storage.local (this device only): your acceptance of the Terms (onboardingDone, consentVersion, consentDate), and per-device overrides for injectOwnAffiliate and remoteRulesEnabled when a synced preference arrives enabled and you decline to inherit it. The reasoning is captured in ADR-0001: consent is an act between you and the device you are using; inheriting it across devices silently would deny each device's user the chance to read and decide.
Stats. Local counters only: URLs cleaned, parameters removed, referrals spotted. Stored in chrome.storage.local. No personally identifiable information. No timestamps tied to individual URLs.
Domain stats. Domain names and parameter counts only. No full URLs, no paths, no timestamps. Stored locally, capped at 50 domains to prevent unbounded growth. Never transmitted.
Affiliate tags. Injected locally by the content script when you visit a supported store with no existing tag. No API call is made to decide when or where to inject. The logic runs entirely in your browser against a list of domains that ships with the extension. If you also enable "Remove all affiliate tags from other sources", MUGA's own tag is preserved only when you also have affiliate injection enabled on this device, symmetric with your stated preference.
MUGA is split into two small components that talk to each other only when strictly necessary:
The content script. Runs in every page (subject to your blacklist/whitelist). It owns the user-visible work: URL cleaning on click, on copy, and on page load; <a ping> attribute removal; redirect-wrapper unwrapping; and (on Firefox) AMP-to-canonical redirect. The cleaning library ships inside the content script as a pre-built bundle (src/content/cleaner-bundle.js), generated from the ES module sources under src/lib/ by tools/bundle-content.mjs. The bundle is committed to the repository so reviewers can verify it matches the unbundled source byte-for-byte.
The service worker. Runs in the background. It owns three things: (1) cross-cutting state that the content script doesn't need to compute itself, namely incrementing the badge counter and stats on a fire-and-forget message (the BADGE_AND_STATS side-channel), (2) cross-page coordination such as the toolbar action surface, the context-menu entries, and the optional weekly remote-rules fetch, and (3) the consent state machine, covering onboarding, soft and hard re-onboard, and the migration from legacy sync-stored consent to local. The service worker does not see or process URLs in transit; cleaning is finished before the service worker hears about it.
Why this split matters for transparency. Most extensions route URLs through their service worker so the worker can decide what to do. MUGA does the opposite: the cleaning runs in the page where you clicked, the service worker hears only the post-cleaning summary it needs to update the badge. There is no central pipeline that observes your URLs.
MUGA records your acceptance of its Terms and Privacy Policy per device, not per browser account. If you install MUGA on a second device, you'll be asked to read and accept the documents on that device too, even if you already accepted them somewhere else. Behavioural preferences (language, toggles) still follow your account through sync, but the consent to act on them is local. The full reasoning is in ADR-0001.
Re-onboarding. If we materially change the Terms, MUGA surfaces a re-acceptance flow on each device the next time the service worker wakes up; affected features are gated until you re-accept. If we only add clauses (without changing existing ones), MUGA shows you the new clauses and lets you accept or decline; declining keeps you under the previously accepted Terms.
Migration on upgrade. If you previously installed MUGA before per-device consent shipped and your acceptance was stored across devices via sync, MUGA migrates that acceptance to local storage on the first run after upgrade. The migration is one-way (sync → local) and idempotent. You keep your acceptance state without re-onboarding. Implemented in src/lib/sync-migration.js.
rules.muga.app via HTTPS GET with credentials: "omit" and cache: "no-store": no browsing data is sent, no cookies, no identifiers of any kind. Interim engineering note: the off-by-default posture is a stabilization choice while the rule-signing infrastructure matures, not a permanent value statement; the default may flip in a future release. When it does, the change will be announced in the changelog and surfaced in the existing soft re-onboard flow so you see it before it takes effect.Every permission in manifest.json has a specific, minimal purpose. Here is what each one is and why it exists.
Every claim on this page is verifiable. You don't have to take our word for it.
npm ci && npm run build. The output is the same extension distributed on the Chrome Web Store and Firefox AMO.tools/bundle-content.mjs, ~30 lines) because MV3 content scripts cannot use ES module imports portably across Chrome and Firefox MV2. The unbundled source lives under src/lib/ and the committed bundle output is src/content/cleaner-bundle.js. Run npm run build:content and diff the regenerated output against the committed file. It must match exactly. The bundle is the only build-time transformation in the project; everything else is plain vanilla JavaScript (no TypeScript, no Babel, no JSX).chrome://extensions in developer mode; Firefox: extract the XPI) and diff the files against the published source. They should match exactly.rules.muga.app per 7 days.AGENTS.md and CONTRIBUTING.md in the repository document the automated review rules that enforce these constraints on every pull request.docs/adr/ captures the reasoning behind decisions like per-device consent. If you want to know why something is built the way it is, the ADRs are where the trade-offs are written down.