from __future__ import annotations from dataclasses import dataclass from pathlib import Path @dataclass class FirefoxHardening: force_proxy: bool = True disable_webrtc: bool = True resist_fingerprinting: bool = True disable_telemetry: bool = True first_party_isolation: bool = True strict_tracking_protection: bool = True clear_on_shutdown: bool = True timezone_utc: bool = True def _bool(v: bool) -> str: return "true" if v else "false" def build_user_js( proxy_host: str, proxy_port: int, hard: FirefoxHardening, ) -> str: lines: list[str] = [ "// Managed by Proxy God. Changes are overwritten on next launch.", 'user_pref("app.normandy.enabled", false);', 'user_pref("app.shield.optoutstudies.enabled", false);', 'user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false);', 'user_pref("browser.newtabpage.activity-stream.telemetry", false);', 'user_pref("browser.ping-centre.telemetry", false);', 'user_pref("toolkit.telemetry.archive.enabled", false);', 'user_pref("toolkit.telemetry.bhrPing.enabled", false);', 'user_pref("toolkit.telemetry.enabled", false);', 'user_pref("toolkit.telemetry.firstShutdownPing.enabled", false);', 'user_pref("toolkit.telemetry.hybridContent.enabled", false);', 'user_pref("toolkit.telemetry.newProfilePing.enabled", false);', 'user_pref("toolkit.telemetry.shutdownPingSender.enabled", false);', 'user_pref("toolkit.telemetry.unified", false);', 'user_pref("datareporting.healthreport.uploadEnabled", false);', 'user_pref("datareporting.policy.dataSubmissionEnabled", false);', 'user_pref("network.trr.mode", 5);', 'user_pref("network.captive-portal-service.enabled", false);', 'user_pref("geo.enabled", false);', 'user_pref("media.peerconnection.enabled", false);', 'user_pref("media.navigator.enabled", false);', 'user_pref("dom.battery.enabled", false);', 'user_pref("dom.gamepad.enabled", false);', 'user_pref("dom.netinfo.enabled", false);', 'user_pref("webgl.disabled", false);', 'user_pref("webgl.enable-debug-renderer-info", false);', ] if hard.force_proxy: lines.extend( [ 'user_pref("network.proxy.type", 1);', f'user_pref("network.proxy.http", "{proxy_host}");', f'user_pref("network.proxy.http_port", {int(proxy_port)});', f'user_pref("network.proxy.ssl", "{proxy_host}");', f'user_pref("network.proxy.ssl_port", {int(proxy_port)});', f'user_pref("network.proxy.socks", "{proxy_host}");', f'user_pref("network.proxy.socks_port", {int(proxy_port)});', 'user_pref("network.proxy.socks_version", 5);', 'user_pref("network.proxy.socks_remote_dns", true);', 'user_pref("network.proxy.no_proxies_on", "");', ] ) if hard.disable_webrtc: lines.extend( [ 'user_pref("media.peerconnection.enabled", false);', 'user_pref("media.peerconnection.ice.default_address_only", true);', 'user_pref("media.peerconnection.ice.no_host", true);', ] ) if hard.resist_fingerprinting: lines.extend( [ 'user_pref("privacy.resistFingerprinting", true);', 'user_pref("privacy.resistFingerprinting.letterboxing", true);', 'user_pref("privacy.window.maxInnerWidth", 1600);', 'user_pref("privacy.window.maxInnerHeight", 900);', ] ) if hard.first_party_isolation: lines.append('user_pref("privacy.firstparty.isolate", true);') if hard.disable_telemetry: lines.append('user_pref("browser.send_pings", false);') if hard.strict_tracking_protection: lines.extend( [ 'user_pref("privacy.trackingprotection.enabled", true);', 'user_pref("privacy.trackingprotection.pbmode.enabled", true);', ] ) if hard.clear_on_shutdown: lines.extend( [ 'user_pref("privacy.sanitize.sanitizeOnShutdown", true);', 'user_pref("privacy.clearOnShutdown.history", true);', 'user_pref("privacy.clearOnShutdown.cookies", true);', 'user_pref("privacy.clearOnShutdown.cache", true);', 'user_pref("privacy.clearOnShutdown.downloads", true);', 'user_pref("privacy.clearOnShutdown.formdata", true);', 'user_pref("privacy.clearOnShutdown.sessions", true);', ] ) if hard.timezone_utc: lines.append('user_pref("privacy.resistFingerprinting.reduceTimerPrecision", true);') lines.append("") return "\n".join(lines) def build_prefs_js_marker() -> str: return ( "// Proxy God managed profile\n" '// Do not edit manually; user.js is rewritten on launch.\n' ) def ensure_firefox_profile( profile_dir: Path, proxy_host: str, proxy_port: int, hard: FirefoxHardening, ) -> None: profile_dir.mkdir(parents=True, exist_ok=True) (profile_dir / "user.js").write_text( build_user_js(proxy_host, proxy_port, hard), encoding="utf-8", ) (profile_dir / "prefs.js").write_text(build_prefs_js_marker(), encoding="utf-8")