Add LAN-broadcast lockdown, per-rotation MAC, leak audit panel

Tier-1 paranoid hardening: privacy_lan.py disables LLMNR/NetBIOS/mDNS with reversible snapshot; service rotates MAC on every chain rotation when enabled; leak_audit.py probes every leak surface (IP, DNS, IPv6, WPAD, GPO, ProxySettingsPerUser, LAN broadcast, VPN, WebRTC) and renders pass/fail in Privacy tab.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Indiana Holmes
2026-05-16 18:40:53 -07:00
parent 7657a37855
commit 6561bdc37f
6 changed files with 756 additions and 0 deletions

View File

@@ -36,6 +36,7 @@ from .firewall import (
request_admin_relaunch,
)
from .dns_leak import check_dns_leak_hint, flush_dns_cache
from .leak_audit import AuditReport, run_audit_sync
from .browser_launcher import (
BrowserConfig,
BrowserSession,
@@ -1083,10 +1084,12 @@ def main() -> None:
"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)
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)
def _priv_toggle(parent: Any, text: str, var: ctk.BooleanVar, tip: str = "") -> ctk.CTkCheckBox:
cb = ctk.CTkCheckBox(
@@ -1100,6 +1103,10 @@ def main() -> None:
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).",
@@ -1118,6 +1125,13 @@ def main() -> None:
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).",
)
fp_card = _priv_section(
"Device fingerprint",
@@ -1163,6 +1177,65 @@ def main() -> None:
_btn(dns_btn_row, "Flush DNS now", _manual_dns_flush, w=110, h=28,
fg_color=DIM).pack(side="left")
audit_card = _priv_section(
"Leak audit (mission-critical)",
"Probes every leak surface: IP, DNS, IPv6, WPAD, Group Policy, "
"ProxySettingsPerUser, LLMNR / NetBIOS / mDNS, VPN, WebRTC policy.",
)
audit_box = ctk.CTkTextbox(
audit_card, height=220, font=("Consolas", 10),
fg_color=BG, text_color=TEXT, scrollbar_button_color=ACCENT,
)
audit_box.pack(fill="x", padx=12, pady=(4, 4))
audit_box.insert("end", "Click 'Run audit' to probe live leak status.")
audit_status_lbl = ctk.CTkLabel(
audit_card, text="", font=(FONT, 11, "bold"), text_color=TEXT2,
)
audit_status_lbl.pack(anchor="w", padx=12, pady=(0, 4))
def _render_audit(rep: AuditReport) -> None:
audit_box.delete("1.0", "end")
for f in rep.findings:
mark = "OK " if f.ok else "LEAK"
line = f"[{mark}] {f.name:<34} {f.value}"
if f.note:
line += f"{f.note}"
audit_box.insert("end", line + "\n")
bad = sum(1 for f in rep.findings if not f.ok)
if rep.overall_ok:
audit_status_lbl.configure(
text="CLEAN — no leaks detected on probed surfaces.", text_color=GREEN
)
else:
audit_status_lbl.configure(
text=f"{bad} leak(s) detected — see report above.", text_color=RED
)
def _run_audit_async() -> None:
audit_status_lbl.configure(text="Running audit…", text_color=YELLOW)
audit_box.delete("1.0", "end")
audit_box.insert("end", "Probing every leak surface in parallel…\n")
def work() -> None:
try:
rep = run_audit_sync(
f"http://{svc.settings.listen_addr()}",
svc.settings.ip_check_url,
timeout_seconds=min(12.0, svc.settings.validation_timeout_seconds),
)
except Exception as e:
root.after(0, lambda: audit_status_lbl.configure(
text=f"Audit failed: {e}", text_color=RED))
return
root.after(0, lambda: _render_audit(rep))
threading.Thread(target=work, daemon=True).start()
audit_btn_row = ctk.CTkFrame(audit_card, fg_color="transparent")
audit_btn_row.pack(fill="x", padx=12, pady=(0, 10))
_btn(audit_btn_row, "Run audit", _run_audit_async, w=120, h=28,
fg_color=ACCENT2, hover_color=ACCENT).pack(side="left", padx=(0, 8))
_btn(priv_scroll, "Save privacy settings", lambda: _save_settings(verbose=True),
w=200, h=34, font=(FONT, 12)).pack(anchor="w", padx=8, pady=12)
@@ -1232,9 +1305,11 @@ def main() -> None:
ks_var.set(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)
lan_var.set(False)
br_force_proxy_var.set(True)
br_disable_webrtc_var.set(True)
br_rfp_var.set(True)
@@ -1300,10 +1375,12 @@ def main() -> None:
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()),
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()),
firefox_path=firefox_path_var.get().strip(),
firefox_profile_dir=firefox_profile_var.get().strip(),
browser_clear_on_close=bool(br_clear_var.get()),