harden macOS networking port
This commit is contained in:
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user