import { Palette } from './theme'; import { PRIVACY_TEST_SCORES, type PrivacyTestScore } from './privacy-scores'; /** * Tier system: a coarse, curated stack rank of inherent privacy strength. * Independent of intent-deliverability (covered separately by * nativePrivateWorks / cctPrivateWorks). Used for default ordering on the * Privacy tab and for the small badge under the browser name. * * - 'highest' (reserved — currently unused) * - 'very-high' Default-on hardening + RFP-grade fingerprint defenses * AND tracker/content blocking out of the box (IronFox) * - 'high' Strong in one or more axes but missing tracker blocking * by default — Brave (shields on, fingerprint farbling), * Vanadium (hardened Chromium, no tracker blocker by default), * Tor Browser (network anonymity, but recommends against * content blockers), Focus (always-private) * - 'medium-high' Always-private OR good in some dimensions only (DDG) * - 'medium' Mainstream browser; private mode works * - 'low' Mainstream browser; we can't request a private session */ export type PrivacyTier = 'highest' | 'very-high' | 'high' | 'medium-high' | 'medium' | 'low'; const TIER_ORDER: Record = { highest: 0, 'very-high': 1, high: 2, 'medium-high': 3, medium: 4, low: 5, }; export const TIER_LABEL: Record = { highest: 'Highest', 'very-high': 'Very high', high: 'High', 'medium-high': 'Mid-high', medium: 'Medium', low: 'Low', }; export function tierWeight(t: PrivacyTier): number { return TIER_ORDER[t]; } /** * Strategy that ATTEMPTS private launch. Whether the chosen browser actually * honors it is encoded separately via nativePrivateWorks / cctPrivateWorks * below. * - 'chromium-class' : sets component = IncognitoTabLauncher (Chrome's class) * - 'chromium-extra' : standard VIEW + EXTRA_OPEN_NEW_INCOGNITO_TAB * - 'firefox' : standard VIEW + private_browsing_mode / legacy hints * - null : no private path at all */ export type PrivateMode = 'chromium-class' | 'chromium-extra' | 'firefox' | null; export type Browser = { id: string; name: string; pkg: string; tint: string; tagline?: string; privateMode: PrivateMode; nativePrivateWorks: boolean; cctPrivateWorks: boolean; alwaysPrivate?: boolean; privacyTier: PrivacyTier; /** * Browser's own internal-flags / config URL — `brave://flags`, * `chrome://flags`, `about:config`, etc. Used by the "Browser config" * preset, where each Flow launches its browser's own settings page * rather than a single shared URL. Undefined → that browser doesn't * expose a config URL via external intent and the Flow is disabled * in this mode. */ configUrl?: string; }; /** * Privacy continuum: normal -> mini -> private -> max (most strict). */ export type PrivacyMode = 'normal' | 'mini' | 'private' | 'max'; export const PRIVACY_MODES: readonly PrivacyMode[] = ['normal', 'mini', 'private', 'max']; export function supportedModes(b: Browser): PrivacyMode[] { if (b.alwaysPrivate) return ['normal', 'mini', 'private', 'max']; const m: PrivacyMode[] = ['normal', 'mini']; if (b.nativePrivateWorks) m.push('private'); if (b.cctPrivateWorks) m.push('max'); return m; } /** True if the browser can deliver any kind of private session. */ export function canDoPrivate(b: Browser): boolean { return !!b.alwaysPrivate || b.nativePrivateWorks || b.cctPrivateWorks; } /** Strongest privacy mode the browser can actually deliver. */ export function maxPrivateMode(b: Browser): PrivacyMode | null { if (b.alwaysPrivate) return 'normal'; // already private regardless if (b.cctPrivateWorks) return 'max'; if (b.nativePrivateWorks) return 'private'; return null; } /** * Plain-English explanation of HOW a browser achieves each privacy mode. * Derived entirely from the same flags that drive the launch code in * modules/default-browser, so the docs can't drift from the actual * behavior. */ export type ModeExplanation = { mode: PrivacyMode; supported: boolean; description: string; }; export type BrowserPrivacyExplanation = { summary: string; protections: string[]; testScore?: PrivacyTestScore; modes: ModeExplanation[]; }; export function explainPrivacy(b: Browser): BrowserPrivacyExplanation { const protections = INTRINSIC_PROTECTIONS[b.id] ?? []; const testScore = PRIVACY_TEST_SCORES[b.pkg]; // Prefer the curated one-line description when we have one — it's // calibrated for the browser's actual character. Falls back to the // dynamic mode-based summary otherwise. const curated = BROWSER_DESCRIPTIONS[b.id]; if (b.alwaysPrivate) { return { summary: curated ?? `${b.name} is private by design. Every tab is an isolated session ` + `regardless of what intent we send.`, protections, testScore, modes: PRIVACY_MODES.map((m) => ({ mode: m, supported: true, description: m === 'normal' || m === 'mini' ? 'Standard launch. Private by the browser itself.' : `${b.name} ignores our hints but is already operating in ` + `private mode.`, })), }; } const intentFor = (m: PrivacyMode): string => { switch (m) { case 'normal': return 'ACTION_VIEW with the URL — the regular launch.'; case 'mini': return ( 'ACTION_VIEW with a CCT_SESSION marker bundle — renders as a ' + 'Custom Tab. Same browser session, minimal toolbar.' ); case 'private': { switch (b.privateMode) { case 'firefox': return ( 'ACTION_VIEW + the "private_browsing_mode = true" extra. ' + 'Fenix-based builds (Firefox 112+) honor it for any caller ' + 'and open a real private window.' ); case 'chromium-extra': return ( 'ACTION_VIEW + EXTRA_OPEN_NEW_INCOGNITO_TAB on the plain ' + 'launcher.' + (b.nativePrivateWorks ? '' : ' This build does not honor the extra on the plain ' + 'launcher — current Chromium gates it the same way ' + 'Chrome does.') ); case 'chromium-class': return ( 'Targets the IncognitoTabLauncher activity component. ' + 'Chrome 79+ blocks non-Google-signed callers via ' + 'ExternalAuthUtils.isGoogleSigned() — so for us this ' + 'silently opens a regular tab.' ); default: return 'No third-party private path is exposed by this browser.'; } } case 'max': return b.cctPrivateWorks ? (b.privateMode === 'chromium-class' ? 'Custom Tab + ENABLE_EPHEMERAL_BROWSING extra. Chrome 137+ ' + 'honors this as a true ephemeral session — no history, ' + 'cookies, or cache persisted.' : 'Custom Tab + EXTRA_OPEN_NEW_INCOGNITO_TAB. Brave-style ' + 'forks render this as a private custom tab.') : 'This browser ignores both the ephemeral and incognito hints ' + 'on CCT requests; would degrade to a regular Custom Tab.'; } }; const isSupported = (m: PrivacyMode): boolean => { if (m === 'normal' || m === 'mini') return true; if (m === 'private') return b.nativePrivateWorks; if (m === 'max') return b.cctPrivateWorks; return false; }; const top = maxPrivateMode(b); const summary = curated ?? (top === 'max' ? `${b.name} delivers strongest privacy via ephemeral / private ` + `Custom Tab.` : top === 'private' ? `${b.name} delivers strongest privacy via a native private window.` : `${b.name} has no third-party private path; only regular browsing ` + `works from external intents.`); return { summary, protections, testScore, modes: PRIVACY_MODES.map((m) => ({ mode: m, supported: isSupported(m), description: intentFor(m), })), }; } /** * Curated default order, privacy-first then by general preference. Users * can reorder in-app; browsers not in the saved order are appended in this * default order. */ export const BROWSERS: Browser[] = [ // ─── Tier: very-high (hardened + tracker blocking by default) ─────── { id: 'ironfox', name: 'IronFox', pkg: 'org.ironfoxoss.ironfox', tint: Palette.accentBright, tagline: 'Hardened Firefox', privateMode: 'firefox', nativePrivateWorks: true, cctPrivateWorks: false, privacyTier: 'very-high', configUrl: 'about:config' }, // ─── Tier: very-high (default tracker blocking + fingerprint defenses) ─ // Brave Shields on by default — tracker / ad / script blocking and // per-session canvas/audio/font farbling. Web-privacy posture sits // alongside IronFox, ahead of Vanadium/Helium which have lighter // defaults on the tracker side. { id: 'brave', name: 'Brave', pkg: 'com.brave.browser', tint: '#fb542b', tagline: 'Privacy shield', privateMode: 'chromium-extra', nativePrivateWorks: false, cctPrivateWorks: true, privacyTier: 'very-high', configUrl: 'brave://flags' }, // ─── Tier: medium-high (security-first, lighter web-tracking defaults) ─ // Vanadium ships EasyList+EasyPrivacy and a hardened posture but no // fingerprint randomization; its strength is OS-level security on // GrapheneOS, not web-tracking defaults. Accepts EXTRA_OPEN_NEW_INCOGNITO_TAB // on plain VIEW intents and honors the ephemeral-CCT extra. User setting // "always open external links in incognito" forces every intent into a // private session — fall-through hardening for users who flip it on. { id: 'vanadium', name: 'Vanadium', pkg: 'app.vanadium.browser', tint: '#5e9bd6', tagline: 'GrapheneOS hardened Chromium', privateMode: 'chromium-extra', nativePrivateWorks: true, cctPrivateWorks: true, privacyTier: 'high', configUrl: 'chrome://flags' }, // Helium — Chromium fork that ports Vanadium hardening patches on top of // the off-device Helium codebase. WebRTC IP shielding on by default, // Manifest V2 so uBlock Origin can be *installed* — but no tracker // blocker out of the box. No MTE / hardened_malloc / JIT-off defaults // either (those live in GrapheneOS). Same private-launch shape as the // other chromium-extra browsers. { id: 'helium', name: 'Helium', pkg: 'io.github.jqssun.helium', tint: '#7aa2f7', tagline: 'Vanadium-patched Chromium', privateMode: 'chromium-extra', nativePrivateWorks: true, cctPrivateWorks: true, privacyTier: 'high', configUrl: 'chrome://flags' }, // Tor Browser provides network-level anonymity but the Tor Project advises // against installing extensions / content blockers — so by default it has // no tracker blocker the way IronFox does. { id: 'tor', name: 'Tor Browser', pkg: 'org.torproject.torbrowser', tint: '#7d4698', tagline: 'Onion routing', privateMode: null, nativePrivateWorks: false, cctPrivateWorks: false, alwaysPrivate: true, privacyTier: 'highest', configUrl: 'about:config' }, { id: 'focus', name: 'Firefox Focus', pkg: 'org.mozilla.focus', tint: '#a4007c', tagline: 'Always-private Firefox', privateMode: null, nativePrivateWorks: false, cctPrivateWorks: false, alwaysPrivate: true, privacyTier: 'high' }, // ─── Tier: medium-high (always-private but uneven scores) ─────────── { id: 'ddg', name: 'DuckDuckGo', pkg: 'com.duckduckgo.mobile.android', tint: '#de5833', tagline: 'Private search', privateMode: null, nativePrivateWorks: false, cctPrivateWorks: false, alwaysPrivate: true, privacyTier: 'high' }, // ─── Tier: medium (private mode works, default state weak) ────────── { id: 'firefox', name: 'Firefox', pkg: 'org.mozilla.firefox', tint: '#ff7139', tagline: 'Mozilla', privateMode: 'firefox', nativePrivateWorks: true, cctPrivateWorks: false, privacyTier: 'medium-high', configUrl: 'about:config' }, // Felice — Fennec F-Droid fork with Mozilla extension signing disabled. // Same Fenix runtime as Firefox stable, identical private-launch shape. { id: 'felice', name: 'Felice', pkg: 'org.mozilla.felice', tint: '#c4a7e7', tagline: 'Fennec fork, no addon signing', privateMode: 'firefox', nativePrivateWorks: true, cctPrivateWorks: false, privacyTier: 'medium-high', configUrl: 'about:config' }, // Beta/Nightly intentionally tier below stable: same engine but extra // pre-release telemetry channels. Stable's privacy score is pulled down // by privacytests data; Beta/Nightly have no measured data, so we // anchor them with a slightly lower tier so the family hierarchy // (stable > Beta > Nightly) stays right after the weighted average. { id: 'firefox-beta', name: 'Firefox Beta', pkg: 'org.mozilla.firefox_beta', tint: '#ff7139', tagline: 'Mozilla Beta', privateMode: 'firefox', nativePrivateWorks: true, cctPrivateWorks: false, privacyTier: 'medium', configUrl: 'about:config' }, { id: 'firefox-nightly', name: 'Firefox Nightly', pkg: 'org.mozilla.fenix', tint: '#0250bb', tagline: 'Mozilla Nightly', privateMode: 'firefox', nativePrivateWorks: true, cctPrivateWorks: false, privacyTier: 'medium', configUrl: 'about:config' }, // ─── Tier: medium (Chrome — ephemeral CCT salvages a single tab) ──── { id: 'chrome', name: 'Chrome', pkg: 'com.android.chrome', tint: '#4285F4', tagline: 'Google', privateMode: 'chromium-class', nativePrivateWorks: false, cctPrivateWorks: true, privacyTier: 'low', configUrl: 'chrome://flags' }, ]; /** * Intrinsic browser-level defenses, curated. Surfaced in the privacy * explanation modal alongside the per-mode intent details. Reflects what * the browser does on its own, not what we request from it. */ /** * Plain-English one-line summary per browser. Surfaced at the top of the * Privacy Features modal so the user gets a calibrated take before * reading the bullet list of intrinsic protections. */ export const BROWSER_DESCRIPTIONS: Partial> = { vanadium: "GrapheneOS-hardened Chromium: MTE, CFI, hardened_malloc, JIT off by default. No built-in tracker blocker — relies on system-level controls.", brave: "Sturdy fully open-source Chromium fork. Brave Shields (tracker / ad / script blocking) and per-session canvas/audio/font randomisation on by default.", chrome: "Canonical Chromium build with Google services. Fastest update cadence and best site isolation, but the weakest default-privacy posture of the group.", ironfox: "Hardened Firefox fork. uBlock Origin pre-installed, RFP-grade fingerprint defenses, JIT off, telemetry / WebGL / WebRTC stripped. Pair with Orbot (Tor) or a VPN to add network-layer anonymity — approaches Tor-level privacy without the relay-latency cost.", helium: "Experimental Chromium fork combining Helium and GrapheneOS Vanadium patches. WebRTC IP shielded by default, Manifest V2 so uBlock Origin installs from the Web Store. No OS-level hardening — devs themselves recommend GrapheneOS + Vanadium for serious threat models.", tor: "Onion-routed Firefox-based browser. Three-hop relay network gives network-layer anonymity; same fingerprint for every user. No tracker blocker by design — content filters would distinguish you.", focus: "Single-tab always-private Firefox. Every session starts fresh; one-tap erase. No add-ons, no syncing, no history. Smaller attack surface than full Firefox.", ddg: "Wraps the system Chromium WebView. Always-private session, DuckDuckGo Tracker Radar blocks third-party trackers, one-tap fire button burns state.", firefox: "Mozilla Firefox stable. Open source under MPL 2.0, Enhanced Tracking Protection (Standard) and Total Cookie Protection on by default.", felice: "Fennec F-Droid fork. Mozilla's mandatory extension signing disabled — install any .xpi. Same Fenix runtime as Firefox, identical privacy posture.", 'firefox-beta': "Firefox pre-release channel. Same Gecko engine as stable, features land ~2 weeks earlier. Updates more often.", 'firefox-nightly': "Firefox bleeding-edge channel. Daily builds from upstream Fenix; instability expected.", }; export const INTRINSIC_PROTECTIONS: Partial> = { tor: [ 'Onion routing through three-hop encrypted relay (Tor network)', 'Resist Fingerprinting (RFP) — all users return the same fingerprint', 'Letterboxing — resists window-size fingerprinting', 'Security levels: Standard / Safer / Safest. Safest disables JS globally.', 'HTTPS-Only Mode (Firefox feature) — HTTPS Everywhere was retired 2022', 'First-party isolation (Total Cookie Protection) built-in', 'Supports browser extensions — the Tor Project advises against installing them (de-anonymisation risk), but the capability exists for NoScript / password managers', ], ironfox: [ 'Strict Enhanced Tracking Protection (ETP Strict) by default', 'uBlock Origin pre-installed and pre-configured', 'RFP-grade fingerprinting protection (canvas / audio / font / locale spoofed)', 'Bundled fonts at build-time — consistent font fingerprint', 'HTTPS-Only Mode by default', 'DNS over HTTPS (Quad9) Max Protection — no fallback', 'Clears history / cache / open tabs on exit', 'JIT disabled by default (toggle available)', 'Network connectivity monitoring + ACCESS_NETWORK_STATE removed', 'Global Privacy Control on by default', 'Stack with Orbot (Tor) or a VPN for network-layer anonymity — closes the IP-leak gap that Tor Browser covers natively', 'Supports browser extensions (uBlock Origin bundled; add password managers, NoScript, etc.)', ], vanadium: [ 'Hardware Memory Tagging (MTE), CFI, strong stack protector, zero-init', 'Strict site isolation by default', 'JavaScript JIT disabled by default (per-site toggle)', 'WebGPU disabled — attack-surface + fingerprint reduction', 'WebRTC IP handling set to most-private value by default', 'High-performance content filtering (EasyList + EasyPrivacy) built in', 'Third-party cookies disabled by default', 'DoNotTrack enabled by default', 'Battery API spoofed (always charging, 100%)', 'Reduced User-Agent + client hints — placeholder values for OS / device / browser version', 'Hybrid post-quantum TLS by default', 'Connects only to GrapheneOS servers by default (no Google services)', 'Honors EXTRA_OPEN_NEW_INCOGNITO_TAB for per-link private launches', 'User setting "always open external links in incognito" — when on, ' + 'every external intent we send opens in a private session regardless', ], helium: [ 'WebRTC IP handling shielded by default — local IPs not leaked to STUN', 'Vanadium security patches ported on top of upstream Chromium', 'Manifest V2 extension support — uBlock Origin installable from Chrome Web Store', 'Chromium site isolation inherited from upstream', 'No telemetry, no Google services integration', 'Open source under GPLv2; GitHub-release distribution', ], brave: [ 'Brave Shields: tracker + ad + script blocking by default', 'Farbling: per-session canvas / audio / font randomization', 'Aggressive shield mode available per-site', 'HTTPS upgrades + cookie blocking by default', 'Strips known tracking parameters from URLs', ], focus: [ 'Always-private: every session starts fresh', 'Tracking Protection on by default', 'Erase button clears all state in one tap', 'Single-tab design — no cross-tab state to leak', 'Supports browser extensions — Fenix runtime, so uBlock Origin / password managers installable', ], ddg: [ 'DuckDuckGo Tracker Radar blocks 100+ trackers', 'Fire button burns all open tabs and stored data', 'Email Protection for hiding addresses', 'Smarter Encryption upgrades HTTP to HTTPS', ], firefox: [ 'Enhanced Tracking Protection (Standard) on by default', 'Total Cookie Protection (cookie jar per site)', 'Private window opens a fresh session', 'Supports browser extensions (uBlock Origin, password managers, etc.)', ], felice: [ 'Same as Firefox — Enhanced Tracking Protection + Total Cookie Protection', 'Mozilla extension signing disabled — install any .xpi without approval', 'Private window opens a fresh session', 'Supports browser extensions without restriction', ], 'firefox-beta': [ 'Same as Firefox + pre-release features', 'Supports browser extensions (uBlock Origin, password managers, etc.)', ], 'firefox-nightly': [ 'Same as Firefox + nightly experiments', 'Supports browser extensions (uBlock Origin, password managers, etc.)', ], chrome: [ 'Safe Browsing on by default', 'Incognito mode (only reachable via Chrome\'s own UI for our caller)', 'Ephemeral Custom Tab on Chrome 137+ (truly stateless when requested)', ], }; /** Privacy-tier-aware default sort. Lower tier weight = more private. */ export function sortByTier(browsers: Browser[]): Browser[] { return [...browsers].sort((a, b) => { const t = tierWeight(a.privacyTier) - tierWeight(b.privacyTier); if (t !== 0) return t; // stable within tier — preserve BROWSERS order return BROWSERS.indexOf(a) - BROWSERS.indexOf(b); }); } /** Lookup privacytests.org score for a package (may be undefined). */ export function getPrivacyTestScore(pkg: string): PrivacyTestScore | undefined { return PRIVACY_TEST_SCORES[pkg]; }