Build comprehensive privacy suite and hardened browser controls.
Adds VPN-aware leak handling, chain testing UX improvements, hardened Firefox launch/profile management, privacy/device hardening modules, and tray/status upgrades so the app is production-ready as the new baseline. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
132
proxy_chain_manager/browser_launcher.py
Normal file
132
proxy_chain_manager/browser_launcher.py
Normal file
@@ -0,0 +1,132 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import shutil
|
||||
import subprocess
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from .browser_profile import FirefoxHardening, ensure_firefox_profile
|
||||
from .paths import app_data_dir
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@dataclass
|
||||
class BrowserConfig:
|
||||
firefox_path: str = ""
|
||||
profile_dir: str = ""
|
||||
clear_on_close: bool = True
|
||||
disposable_profile: bool = False
|
||||
kill_on_chain_drop: bool = True
|
||||
auto_relaunch: bool = False
|
||||
lock_managed_profile: bool = True
|
||||
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
|
||||
timezone_utc: bool = True
|
||||
|
||||
|
||||
def default_firefox_path() -> str:
|
||||
candidates = [
|
||||
Path(r"C:\Program Files\Mozilla Firefox\firefox.exe"),
|
||||
Path(r"C:\Program Files (x86)\Mozilla Firefox\firefox.exe"),
|
||||
]
|
||||
for c in candidates:
|
||||
if c.is_file():
|
||||
return str(c)
|
||||
return ""
|
||||
|
||||
|
||||
def default_profile_dir() -> str:
|
||||
return str(app_data_dir() / "browser_profiles" / "firefox_hardened")
|
||||
|
||||
|
||||
class BrowserSession:
|
||||
def __init__(self) -> None:
|
||||
self._proc: subprocess.Popen[str] | None = None
|
||||
self._profile_path: Path | None = None
|
||||
|
||||
def is_running(self) -> bool:
|
||||
return self._proc is not None and self._proc.poll() is None
|
||||
|
||||
def pid(self) -> int | None:
|
||||
if self._proc is None:
|
||||
return None
|
||||
return self._proc.pid
|
||||
|
||||
def launch(
|
||||
self,
|
||||
cfg: BrowserConfig,
|
||||
proxy_host: str,
|
||||
proxy_port: int,
|
||||
) -> tuple[bool, str]:
|
||||
raw_exe = (cfg.firefox_path or default_firefox_path()).strip().strip('"').strip("'")
|
||||
exe = Path(raw_exe)
|
||||
if not exe.is_file():
|
||||
return False, f"Firefox executable not found: {raw_exe or '(empty path)'}"
|
||||
raw_profile = (cfg.profile_dir or default_profile_dir()).strip().strip('"').strip("'")
|
||||
profile_dir = Path(raw_profile)
|
||||
hard = FirefoxHardening(
|
||||
force_proxy=cfg.force_proxy,
|
||||
disable_webrtc=cfg.disable_webrtc,
|
||||
resist_fingerprinting=cfg.resist_fingerprinting,
|
||||
disable_telemetry=cfg.disable_telemetry,
|
||||
first_party_isolation=cfg.first_party_isolation,
|
||||
strict_tracking_protection=cfg.strict_tracking_protection,
|
||||
clear_on_shutdown=cfg.clear_on_close,
|
||||
timezone_utc=cfg.timezone_utc,
|
||||
)
|
||||
if cfg.lock_managed_profile:
|
||||
ensure_firefox_profile(profile_dir, proxy_host, int(proxy_port), hard)
|
||||
if self.is_running():
|
||||
self.stop()
|
||||
if cfg.lock_managed_profile:
|
||||
args = [str(exe), "-no-remote", "-profile", str(profile_dir)]
|
||||
else:
|
||||
args = [str(exe)]
|
||||
try:
|
||||
self._proc = subprocess.Popen(
|
||||
args,
|
||||
creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0),
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True,
|
||||
)
|
||||
# Smoke-check: catch immediate startup crashes and report clearly.
|
||||
time.sleep(0.4)
|
||||
rc = self._proc.poll()
|
||||
if rc is not None:
|
||||
self._proc = None
|
||||
return False, f"Firefox exited immediately (code {rc}). Check path/profile."
|
||||
self._profile_path = profile_dir if cfg.lock_managed_profile else None
|
||||
log.info("Launched hardened Firefox pid=%s profile=%s", self._proc.pid, profile_dir)
|
||||
if cfg.lock_managed_profile:
|
||||
return True, f"Hardened Firefox started (pid {self._proc.pid})."
|
||||
return True, f"Firefox started (unlocked mode, pid {self._proc.pid})."
|
||||
except Exception as e:
|
||||
self._proc = None
|
||||
return False, f"Launch failed: {e!s}"
|
||||
|
||||
def stop(self, dispose: bool = False) -> tuple[bool, str]:
|
||||
if self._proc and self._proc.poll() is None:
|
||||
try:
|
||||
self._proc.terminate()
|
||||
self._proc.wait(timeout=8)
|
||||
except Exception:
|
||||
try:
|
||||
self._proc.kill()
|
||||
except Exception:
|
||||
pass
|
||||
finally:
|
||||
self._proc = None
|
||||
if dispose and self._profile_path and self._profile_path.exists():
|
||||
try:
|
||||
shutil.rmtree(self._profile_path, ignore_errors=True)
|
||||
except Exception:
|
||||
pass
|
||||
return True, "Browser stopped."
|
||||
Reference in New Issue
Block a user