Spoof-when-possible browser personas, Tier-2 telemetry kill + artifact wipe, UI polish
Browser: identity persona dropdown (blend Windows-Chrome / Firefox / mac-Safari / hardened / custom) + cookie policy dropdown. Blend personas relax RFP/FPI/strict-TP so the machine looks normal while still hiding behind the chain. Persona TZ applied via env var; UA / language / locale / screen via user.js. WebRTC still forced off (it leaks real IP). Privacy: Telemetry kill toggle (DiagTrack, Activity History, ad ID, Cortana, scheduled tasks) with full snapshot/restore. One-button forensic artifact wipe (TEMP, Recent, Jump Lists, Prefetch, MRU, clipboard). UI: neon palette, glow card borders, gradient header banner. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,13 +1,24 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
from .browser_identity import (
|
||||
Persona,
|
||||
cookie_behavior_value,
|
||||
cookie_session_only,
|
||||
get_persona,
|
||||
is_blend_persona,
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class FirefoxHardening:
|
||||
# Proxy & network always-on
|
||||
force_proxy: bool = True
|
||||
disable_webrtc: bool = True
|
||||
disable_webrtc: bool = True # always strongly recommended; leaks real IP
|
||||
|
||||
# Hardened block mode — overrides persona spoofing
|
||||
resist_fingerprinting: bool = True
|
||||
disable_telemetry: bool = True
|
||||
first_party_isolation: bool = True
|
||||
@@ -15,6 +26,46 @@ class FirefoxHardening:
|
||||
clear_on_shutdown: bool = True
|
||||
timezone_utc: bool = True
|
||||
|
||||
# Persona / cookie selectors (added in "spoof when possible" mode)
|
||||
persona_key: str = "custom" # see browser_identity.PERSONAS
|
||||
cookie_mode: str = "block_third_party" # see browser_identity.COOKIE_MODES
|
||||
|
||||
# Optional Firefox-managed user-agent override even outside of a persona
|
||||
user_agent_override: str = ""
|
||||
|
||||
# Filled in by ``apply_persona`` when persona_key starts with "blend_".
|
||||
persona: Persona | None = field(default=None, repr=False, compare=False)
|
||||
|
||||
|
||||
def apply_persona(hard: FirefoxHardening) -> FirefoxHardening:
|
||||
"""Mutate hardening flags consistent with the persona choice.
|
||||
|
||||
Blend-in personas relax the "stand out" toggles (RFP, FPI, strict TP) but
|
||||
keep the IP-leakers off (WebRTC, geolocation, etc.). Cookie mode is
|
||||
honored. The result is a profile that looks like a normal browser.
|
||||
"""
|
||||
if hard.persona_key == "hardened":
|
||||
hard.resist_fingerprinting = True
|
||||
hard.first_party_isolation = True
|
||||
hard.strict_tracking_protection = True
|
||||
hard.timezone_utc = True
|
||||
hard.persona = None
|
||||
return hard
|
||||
|
||||
if is_blend_persona(hard.persona_key):
|
||||
hard.resist_fingerprinting = False # RFP itself is a fingerprint
|
||||
hard.first_party_isolation = False # breaks logins on many sites
|
||||
hard.strict_tracking_protection = False # Firefox default ETP is enough
|
||||
hard.timezone_utc = False
|
||||
hard.persona = get_persona(hard.persona_key)
|
||||
if hard.persona and not hard.user_agent_override:
|
||||
hard.user_agent_override = hard.persona.user_agent
|
||||
return hard
|
||||
|
||||
# custom — honor the individual flags as-is.
|
||||
hard.persona = None
|
||||
return hard
|
||||
|
||||
|
||||
def _bool(v: bool) -> str:
|
||||
return "true" if v else "false"
|
||||
@@ -25,8 +76,10 @@ def build_user_js(
|
||||
proxy_port: int,
|
||||
hard: FirefoxHardening,
|
||||
) -> str:
|
||||
hard = apply_persona(hard)
|
||||
lines: list[str] = [
|
||||
"// Managed by Proxy God. Changes are overwritten on next launch.",
|
||||
# Always-on baseline (no UX cost)
|
||||
'user_pref("app.normandy.enabled", false);',
|
||||
'user_pref("app.shield.optoutstudies.enabled", false);',
|
||||
'user_pref("browser.newtabpage.activity-stream.feeds.telemetry", false);',
|
||||
@@ -45,14 +98,14 @@ def build_user_js(
|
||||
'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);',
|
||||
# WebGL allowed by default (blocking it makes you stand out massively)
|
||||
'user_pref("webgl.enable-debug-renderer-info", false);',
|
||||
]
|
||||
|
||||
if hard.force_proxy:
|
||||
lines.extend(
|
||||
[
|
||||
@@ -71,7 +124,9 @@ def build_user_js(
|
||||
'user_pref("network.proxy.no_proxies_on", "");',
|
||||
]
|
||||
)
|
||||
|
||||
if hard.disable_webrtc:
|
||||
# Off regardless of persona — WebRTC leaks real IP through STUN.
|
||||
lines.extend(
|
||||
[
|
||||
'user_pref("media.peerconnection.enabled", false);',
|
||||
@@ -79,6 +134,7 @@ def build_user_js(
|
||||
'user_pref("media.peerconnection.ice.no_host", true);',
|
||||
]
|
||||
)
|
||||
|
||||
if hard.resist_fingerprinting:
|
||||
lines.extend(
|
||||
[
|
||||
@@ -88,10 +144,13 @@ def build_user_js(
|
||||
'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(
|
||||
[
|
||||
@@ -99,6 +158,38 @@ def build_user_js(
|
||||
'user_pref("privacy.trackingprotection.pbmode.enabled", true);',
|
||||
]
|
||||
)
|
||||
|
||||
# Persona spoofing (UA, accept-language, screen, platform)
|
||||
if hard.persona is not None:
|
||||
p = hard.persona
|
||||
lines.extend(
|
||||
[
|
||||
f'user_pref("general.useragent.override", "{p.user_agent}");',
|
||||
f'user_pref("intl.accept_languages", "{p.accept_language}");',
|
||||
f'user_pref("general.useragent.locale", "{p.locale}");',
|
||||
'user_pref("javascript.use_us_english_locale", true);',
|
||||
f'user_pref("privacy.window.maxInnerWidth", {int(p.screen_w)});',
|
||||
f'user_pref("privacy.window.maxInnerHeight", {int(p.screen_h)});',
|
||||
# Hint timezone — Firefox honors TZ env var at launch (set by
|
||||
# the launcher) but this pref nudges some site detection too.
|
||||
f'user_pref("intl.locale.requested", "{p.locale}");',
|
||||
]
|
||||
)
|
||||
elif hard.user_agent_override:
|
||||
lines.append(
|
||||
f'user_pref("general.useragent.override", "{hard.user_agent_override}");'
|
||||
)
|
||||
|
||||
# Cookie policy (persona-independent)
|
||||
behavior = cookie_behavior_value(hard.cookie_mode)
|
||||
lines.extend(
|
||||
[
|
||||
f'user_pref("network.cookie.cookieBehavior", {behavior});',
|
||||
# 0 = expire normally, 2 = current session only
|
||||
f'user_pref("network.cookie.lifetimePolicy", {2 if cookie_session_only(hard.cookie_mode) else 0});',
|
||||
]
|
||||
)
|
||||
|
||||
if hard.clear_on_shutdown:
|
||||
lines.extend(
|
||||
[
|
||||
@@ -111,12 +202,26 @@ def build_user_js(
|
||||
'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 persona_env(hard: FirefoxHardening) -> dict[str, str]:
|
||||
"""Environment overrides applied when launching Firefox under a persona.
|
||||
|
||||
Only the timezone needs the OS-level env var (TZ) — Firefox reads it
|
||||
even when running under Windows.
|
||||
"""
|
||||
hard = apply_persona(hard)
|
||||
if hard.persona is None:
|
||||
return {}
|
||||
return {"TZ": hard.persona.timezone}
|
||||
|
||||
|
||||
def ensure_firefox_profile(
|
||||
profile_dir: Path,
|
||||
proxy_host: str,
|
||||
|
||||
Reference in New Issue
Block a user