harden macOS networking port

This commit is contained in:
drjones
2026-05-23 22:09:43 -07:00
parent 1f5e63ca1f
commit 35899ba1d1
24 changed files with 581 additions and 742 deletions

View File

@@ -138,6 +138,7 @@ TEXT = "#eaf2ff"
TEXT2 = "#7a8aab"
GLOW = "#0a87ff"
FONT = "Segoe UI"
IS_MAC = sys.platform == "darwin"
def _ts() -> str:
@@ -314,7 +315,7 @@ def _main_inner() -> None:
from .firewall import is_admin as _is_admin
except Exception:
_is_admin = lambda: True # noqa: E731
if svc.settings.kill_switch_enabled and not _is_admin():
if (not IS_MAC) and svc.settings.kill_switch_enabled and not _is_admin():
from tkinter import messagebox
if messagebox.askyesno(
"Kill-switch requires Admin",
@@ -340,26 +341,26 @@ def _main_inner() -> None:
# Boot buttons
ctk.CTkLabel(topbar, text="", text_color=DIM).pack(side="left", padx=4)
boot_lbl = ctk.CTkLabel(topbar, text="Boot:?", font=(FONT, 10), text_color=TEXT2)
boot_lbl = ctk.CTkLabel(topbar, text=("Login:?" if IS_MAC else "Boot:?"), font=(FONT, 10), text_color=TEXT2)
boot_lbl.pack(side="left", padx=2)
def _refresh_boot() -> None:
on = task_exists()
boot_lbl.configure(text="Boot:ON" if on else "Boot:OFF",
boot_lbl.configure(text=("Login:ON" if on else "Login:OFF") if IS_MAC else ("Boot:ON" if on else "Boot:OFF"),
text_color=GREEN if on else DIM)
def _inst_boot() -> None:
ok, msg = install_logon_task()
_log("Boot task installed." if ok else f"Boot install: {msg}")
_log(("Login item installed." if ok else f"Login item install: {msg}") if IS_MAC else ("Boot task installed." if ok else f"Boot install: {msg}"))
_refresh_boot()
def _rm_boot() -> None:
ok, msg = uninstall_logon_task()
_log("Boot task removed." if ok else f"Boot remove: {msg}")
_log(("Login item removed." if ok else f"Login item remove: {msg}") if IS_MAC else ("Boot task removed." if ok else f"Boot remove: {msg}"))
_refresh_boot()
_btn(topbar, "Boot+", _inst_boot, w=54).pack(side="left", padx=2)
_btn(topbar, "Boot", _rm_boot, w=54).pack(side="left", padx=2)
_btn(topbar, "Login+" if IS_MAC else "Boot+", _inst_boot, w=64 if IS_MAC else 54).pack(side="left", padx=2)
_btn(topbar, "Login" if IS_MAC else "Boot", _rm_boot, w=64 if IS_MAC else 54).pack(side="left", padx=2)
# Right side indicators
rotation_lbl = ctk.CTkLabel(topbar, text="rot: 0", font=(FONT, 10), text_color=TEXT2)
@@ -373,7 +374,7 @@ def _main_inner() -> None:
proxy_lbl.pack(side="right", padx=(4, 4))
ctk.CTkLabel(topbar, text="proxy:", font=(FONT, 10), text_color=TEXT2).pack(side="right")
fw_lbl = ctk.CTkLabel(topbar, text="FW:—", font=(FONT, 10), text_color=DIM)
fw_lbl = ctk.CTkLabel(topbar, text="KS:N/A" if IS_MAC else "FW:—", font=(FONT, 10), text_color=DIM)
fw_lbl.pack(side="right", padx=6)
sys_lbl = ctk.CTkLabel(topbar, text="SYS:—", font=(FONT, 10), text_color=DIM)
@@ -381,13 +382,13 @@ def _main_inner() -> None:
admin_badge = ctk.CTkLabel(
topbar,
text="⚡ ADMIN" if is_admin() else "👤 USER",
text="macOS" if IS_MAC else ("⚡ ADMIN" if is_admin() else "👤 USER"),
font=(FONT, 10, "bold"),
text_color=GREEN if is_admin() else YELLOW,
text_color=GREEN if (IS_MAC or is_admin()) else YELLOW,
)
admin_badge.pack(side="right", padx=4)
if not is_admin():
if (not IS_MAC) and not is_admin():
def _elevate() -> None:
# Persist UI state to disk before handing off to the elevated
# process — otherwise unsaved edits in Chain Builder / Settings
@@ -2340,16 +2341,18 @@ def _main_inner() -> None:
toggles_card = _priv_section(
"Protections while chain is running",
"Applied on Start, restored on Stop. macOS prompts for approval when network identity changes need it."
if IS_MAC else
"Applied on Start, restored on Stop. Admin required for MAC, hostname, IPv6, and WebRTC.",
)
mac_var = ctk.BooleanVar(value=s.mac_spoof_enabled)
mac_rotate_var = ctk.BooleanVar(value=s.mac_rotate_on_chain_rotate)
mac_var = ctk.BooleanVar(value=False if IS_MAC else s.mac_spoof_enabled)
mac_rotate_var = ctk.BooleanVar(value=False if IS_MAC else s.mac_rotate_on_chain_rotate)
host_var = ctk.BooleanVar(value=s.spoof_hostname_enabled)
dns_flush_var = ctk.BooleanVar(value=s.flush_dns_on_rotate)
ipv6_var = ctk.BooleanVar(value=s.disable_ipv6_while_active)
webrtc_var = ctk.BooleanVar(value=s.harden_webrtc_enabled)
lan_var = ctk.BooleanVar(value=s.lan_lockdown_enabled)
telemetry_var = ctk.BooleanVar(value=s.telemetry_kill_enabled)
webrtc_var = ctk.BooleanVar(value=False if IS_MAC else s.harden_webrtc_enabled)
lan_var = ctk.BooleanVar(value=False if IS_MAC else s.lan_lockdown_enabled)
telemetry_var = ctk.BooleanVar(value=False if IS_MAC else s.telemetry_kill_enabled)
def _priv_toggle(parent: Any, text: str, var: ctk.BooleanVar, tip: str = "") -> ctk.CTkCheckBox:
cb = ctk.CTkCheckBox(
@@ -2359,17 +2362,19 @@ def _main_inner() -> None:
cb.pack(anchor="w", padx=12, pady=6)
return cb
if not IS_MAC:
_priv_toggle(
toggles_card, "Randomize MAC addresses on physical adapters", mac_var,
"Changes NIC MAC values while chain runs (admin required).",
)
_priv_toggle(
toggles_card, "Re-randomize MAC on EVERY chain rotation (paranoid)", mac_rotate_var,
"Mutates MAC every time the chain rotates — prevents long-session correlation.",
)
_priv_toggle(
toggles_card, "Randomize MAC addresses on physical adapters", mac_var,
"Changes NIC MAC values while chain runs (admin required).",
)
_priv_toggle(
toggles_card, "Re-randomize MAC on EVERY chain rotation (paranoid)", mac_rotate_var,
"Mutates MAC every time the chain rotates — prevents long-session correlation.",
)
_priv_toggle(
toggles_card, "Spoof computer / NetBIOS hostname", host_var,
"Temporarily renames machine identity while active (admin required).",
toggles_card, "Spoof computer hostname" if IS_MAC else "Spoof computer / NetBIOS hostname", host_var,
"Temporarily renames macOS ComputerName, HostName, and LocalHostName while active."
if IS_MAC else "Temporarily renames machine identity while active (admin required).",
)
_priv_toggle(
toggles_card, "Flush DNS cache when starting or rotating chains", dns_flush_var,
@@ -2377,28 +2382,39 @@ def _main_inner() -> None:
)
_priv_toggle(
toggles_card, "Disable IPv6 on active adapters", ipv6_var,
"Turns off IPv6 bindings while active to reduce IPv6 leak paths.",
)
_priv_toggle(
toggles_card,
"Harden WebRTC in Chrome / Edge (block non-proxied UDP)",
webrtc_var,
"Applies Windows policy to block direct WebRTC UDP bypass in Chromium browsers.",
)
_priv_toggle(
toggles_card,
"LAN lockdown: kill LLMNR / NetBIOS / mDNS hostname broadcasts",
lan_var,
"Stops your machine from advertising its hostname on the local network "
"(admin required, reversible on stop).",
)
_priv_toggle(
toggles_card,
"Telemetry kill: DiagTrack, Activity History, Cortana web, ad ID",
telemetry_var,
"Stops Windows telemetry pipelines while chain is up. Reversible on stop "
"(snapshot of original state taken).",
"Turns off IPv6 with networksetup while active, then restores Automatic/Link-local state on Stop."
if IS_MAC else "Turns off IPv6 bindings while active to reduce IPv6 leak paths.",
)
if IS_MAC:
ctk.CTkLabel(
toggles_card,
text="Removed on macOS: global firewall kill-switch, MAC spoof, Chrome/Edge registry WebRTC policy, LAN lockdown, and Windows telemetry kill. Browser WebRTC hardening is handled by the Firefox profile in the Browser tab.",
font=(FONT, 10),
text_color=TEXT2,
wraplength=860,
justify="left",
).pack(anchor="w", padx=12, pady=(8, 10))
else:
_priv_toggle(
toggles_card,
"Harden WebRTC in Chrome / Edge (block non-proxied UDP)",
webrtc_var,
"Applies Windows policy to block direct WebRTC UDP bypass in Chromium browsers.",
)
_priv_toggle(
toggles_card,
"LAN lockdown: kill LLMNR / NetBIOS / mDNS hostname broadcasts",
lan_var,
"Stops your machine from advertising its hostname on the local network "
"(admin required, reversible on stop).",
)
_priv_toggle(
toggles_card,
"Telemetry kill: DiagTrack, Activity History, Cortana web, ad ID",
telemetry_var,
"Stops Windows telemetry pipelines while chain is up. Reversible on stop "
"(snapshot of original state taken).",
)
fp_card = _priv_section(
"Device fingerprint",
@@ -2492,7 +2508,8 @@ def _main_inner() -> None:
# ── WebRTC leak test ─────────────────────────────────────────────────────
rtc_card = _priv_section(
"WebRTC leak test",
"Scans Chrome/Edge policy, Firefox profile prefs, and live STUN server reachability.",
"Scans Firefox profile prefs and live STUN reachability."
if IS_MAC else "Scans Chrome/Edge policy, Firefox profile prefs, and live STUN server reachability.",
)
rtc_box = ctk.CTkTextbox(
rtc_card, height=110, font=("Consolas", 10),
@@ -2536,12 +2553,16 @@ def _main_inner() -> None:
fg_color=ACCENT2, hover_color=ACCENT).pack(side="left")
wipe_card = _priv_section(
"Forensic artifact wipe",
"One-button purge of common Windows breadcrumb trails. Irreversible.",
"Local artifact cleanup" if IS_MAC else "Forensic artifact wipe",
"Purges app temp/log traces and clipboard on macOS. Irreversible."
if IS_MAC else "One-button purge of common Windows breadcrumb trails. Irreversible.",
)
wipe_result_lbl = ctk.CTkLabel(
wipe_card, text="Click 'Wipe now' to purge %TEMP%, Recent, Jump Lists, "
"Prefetch (Admin), MRU lists, and the clipboard.",
wipe_card,
text=("Click 'Wipe now' to purge app temp/log traces and the clipboard."
if IS_MAC else
"Click 'Wipe now' to purge %TEMP%, Recent, Jump Lists, "
"Prefetch (Admin), MRU lists, and the clipboard."),
font=(FONT, 10), text_color=TEXT2, wraplength=880, justify="left",
)
wipe_result_lbl.pack(anchor="w", padx=12, pady=8)
@@ -2566,6 +2587,8 @@ def _main_inner() -> None:
audit_card = _priv_section(
"Leak audit (mission-critical)",
"Probes macOS-relevant surfaces: IP, DNS, IPv6, system proxy, VPN, browser WebRTC profile, and chain exit."
if IS_MAC else
"Probes every leak surface: IP, DNS, IPv6, WPAD, Group Policy, "
"ProxySettingsPerUser, LLMNR / NetBIOS / mDNS, VPN, WebRTC policy.",
)
@@ -2674,15 +2697,25 @@ def _main_inner() -> None:
_fld(val_sec, "Per-proxy timeout (sec)", "timeout", str(s.validation_timeout_seconds))
sec_sec = _section("Security")
ks_row = ctk.CTkFrame(sec_sec, fg_color="transparent")
ks_row.pack(fill="x", padx=12, pady=8)
ks_var = ctk.BooleanVar(value=s.kill_switch_enabled)
ctk.CTkCheckBox(
ks_row,
text="Firewall kill-switch (block ALL traffic if chain is down — requires Admin)",
variable=ks_var, font=(FONT, 11),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT,
).pack(side="left")
ks_var = ctk.BooleanVar(value=False if IS_MAC else s.kill_switch_enabled)
if IS_MAC:
ctk.CTkLabel(
sec_sec,
text="macOS mode uses loopback GOST, system proxy enforcement, hardened Firefox profiles, DNS flushing, and exit verification. The Windows netsh kill-switch is removed because there is no clean equivalent that can be safely toggled from a user app without risking a stuck network state.",
font=(FONT, 10),
text_color=TEXT2,
wraplength=880,
justify="left",
).pack(anchor="w", padx=12, pady=8)
else:
ks_row = ctk.CTkFrame(sec_sec, fg_color="transparent")
ks_row.pack(fill="x", padx=12, pady=8)
ctk.CTkCheckBox(
ks_row,
text="Firewall kill-switch (block ALL traffic if chain is down — requires Admin)",
variable=ks_var, font=(FONT, 11),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT,
).pack(side="left")
# Emergency disengage — for crashes where the kill-switch lingers and
# the operator needs internet back fast without restarting the app.
@@ -2703,27 +2736,28 @@ def _main_inner() -> None:
messagebox.showinfo("Kill-switch", "Firewall rules removed.")
_log("Emergency disengage: firewall rules removed by operator.")
ks_btn_row = ctk.CTkFrame(sec_sec, fg_color="transparent")
ks_btn_row.pack(fill="x", padx=12, pady=(0, 8))
_btn(
ks_btn_row, "⚠ Emergency disengage firewall now",
_emergency_disengage_now,
w=320, h=28,
fg_color="#7f1d1d", hover_color="#991b1b",
).pack(side="left")
if not IS_MAC:
ks_btn_row = ctk.CTkFrame(sec_sec, fg_color="transparent")
ks_btn_row.pack(fill="x", padx=12, pady=(0, 8))
_btn(
ks_btn_row, "⚠ Emergency disengage firewall now",
_emergency_disengage_now,
w=320, h=28,
fg_color="#7f1d1d", hover_color="#991b1b",
).pack(side="left")
def _apply_point_and_shoot() -> None:
"""Simple safe defaults: secure + low-friction launch profile."""
use_manual_var.set(True)
mode_var.set("auto")
elite_var.set(False)
ks_var.set(True)
ks_var.set(False if IS_MAC else True)
dns_flush_var.set(True)
mac_var.set(False)
mac_rotate_var.set(False)
host_var.set(False)
ipv6_var.set(False)
webrtc_var.set(True)
webrtc_var.set(False if IS_MAC else True)
lan_var.set(False)
telemetry_var.set(False)
persona_var.set(PERSONA_LABELS["blend_windows_chrome"])
@@ -2788,18 +2822,18 @@ def _main_inner() -> None:
max_candidates=max(10, int(entries["maxc"].get().strip())),
validation_timeout_seconds=min(120.0, max(2.0, float(entries["timeout"].get().strip()))),
prefer_elite=bool(elite_var.get()),
kill_switch_enabled=bool(ks_var.get()),
kill_switch_enabled=False if IS_MAC else bool(ks_var.get()),
proxy_bypass=entries["bypass"].get().strip() or Settings().proxy_bypass,
sources=src_list or Settings().sources,
ip_check_url=entries["check_url"].get().strip() or Settings().ip_check_url,
mac_spoof_enabled=bool(mac_var.get()),
mac_rotate_on_chain_rotate=bool(mac_rotate_var.get()),
mac_spoof_enabled=False if IS_MAC else bool(mac_var.get()),
mac_rotate_on_chain_rotate=False if IS_MAC else bool(mac_rotate_var.get()),
spoof_hostname_enabled=bool(host_var.get()),
flush_dns_on_rotate=bool(dns_flush_var.get()),
disable_ipv6_while_active=bool(ipv6_var.get()),
harden_webrtc_enabled=bool(webrtc_var.get()),
lan_lockdown_enabled=bool(lan_var.get()),
telemetry_kill_enabled=bool(telemetry_var.get()),
harden_webrtc_enabled=False if IS_MAC else bool(webrtc_var.get()),
lan_lockdown_enabled=False if IS_MAC else bool(lan_var.get()),
telemetry_kill_enabled=False if IS_MAC else bool(telemetry_var.get()),
firefox_path=firefox_path_var.get().strip(),
firefox_profile_dir=firefox_profile_var.get().strip(),
browser_clear_on_close=bool(br_clear_var.get()),
@@ -2836,6 +2870,9 @@ def _main_inner() -> None:
text_color=GREEN if on else DIM)
def _refresh_fw(engaged: bool | None = None) -> None:
if IS_MAC:
fw_lbl.configure(text="KS:N/A", text_color=DIM)
return
if engaged is None:
engaged = fw_is_engaged()
fw_lbl.configure(text="FW:ON" if engaged else "FW:OFF",
@@ -3019,7 +3056,10 @@ def _main_inner() -> None:
browser.stop(dispose=bool(br_disposable_var.get()))
tray.stop()
svc.stop()
clear_system_proxy()
try:
clear_system_proxy()
except Exception as exc: # noqa: BLE001
_log(f"System proxy clear failed on quit: {exc}")
if is_admin() and fw_is_engaged():
fw_disengage()
root.destroy()
@@ -3030,12 +3070,20 @@ def _main_inner() -> None:
chain_alive = bool(svc.current_chain) or fw_is_engaged()
if chain_alive:
from tkinter import messagebox
choice = messagebox.askyesnocancel(
"Proxy God still running",
body = (
"The proxy chain is still active.\n\n"
" • Yes — hide window (chain keeps running)\n"
" • No — fully quit (stop chain + clear system proxy)\n"
" • Cancel — keep window open"
if IS_MAC else
"The proxy chain and/or kill-switch are still active.\n\n"
" • Yes — minimize to tray (chain keeps running)\n"
" • No — fully quit (stop chain + remove firewall rules)\n"
" • Cancel — keep window open",
" • Cancel — keep window open"
)
choice = messagebox.askyesnocancel(
"Proxy God still running",
body,
)
if choice is None:
return
@@ -3057,13 +3105,13 @@ def _main_inner() -> None:
_refresh_fingerprint()
_log("" * 60)
_log("Proxy God v2 — ready.")
_log(f"Admin: {'YES — kill-switch + privacy hardening available' if is_admin() else 'NO — run as Admin for full privacy tools'}")
_log("macOS networking: system proxy uses networksetup and will prompt for approval if required." if IS_MAC else f"Admin: {'YES — kill-switch + privacy hardening available' if is_admin() else 'NO — run as Admin for full privacy tools'}")
_log(f"Log file: {LOG_PATH}")
_log("Press START to fetch, validate and chain proxies.")
_log("Chain Builder → build your chain, Test entire chain, then Start.")
_log("Browser tab → launch hardened Firefox profile that follows your chain.")
_log("Signup Prep → open signup pages with autofill (Google / Proton / HydraProxy / custom).")
_log("Privacy tab → MAC, hostname, IPv6, WebRTC, fingerprint audit, DNS checks.")
_log("Privacy tab → hostname, IPv6, DNS/WebRTC checks, fingerprint audit." if IS_MAC else "Privacy tab → MAC, hostname, IPv6, WebRTC, fingerprint audit, DNS checks.")
_log("Works with or without VPN — leak detection adapts automatically.")
_log("" * 60)
_pump()