Files
proxy-god/proxy_chain_manager/app.py
Indiana Holmes 8f012402a6 Harden Windows Server compatibility and system-wide proxy.
Fix exit-IP checks for HTTP-only proxies, apply proxy via WinINet Connections blob and WinHTTP, improve VPN detection on legacy Server, add SOCKS5 host:port:user:pass exit parsing, and add win_compat probe for PowerShell 2.0 hosts.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 18:03:30 -07:00

1531 lines
65 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Proxy God — GUI
Tabs: Live | Chain Builder | Browser | Privacy | Settings
"""
from __future__ import annotations
import logging
import queue
import sys
import threading
import time
from dataclasses import asdict
from datetime import datetime
from typing import Any, Literal
from tkinter import filedialog
import customtkinter as ctk
from .config import (
LISTEN_HOST,
OBFUSCATION_LABELS,
OBFUSCATION_MODES,
Settings,
load_settings,
merge_proxy_credentials,
normalize_proxy_url,
parse_exit_proxy_input,
redact_proxy_url,
save_settings,
split_proxy_for_edit,
)
from .firewall import (
disengage as fw_disengage,
is_admin,
is_engaged as fw_is_engaged,
request_admin_relaunch,
)
from .dns_leak import check_dns_leak_hint, flush_dns_cache
from .browser_launcher import (
BrowserConfig,
BrowserSession,
default_firefox_path,
default_profile_dir,
)
from .fingerprint import audit_device
from .gui_validate import run_validate_each_sync
from .paths import app_data_dir
from .proxy_picker import show_proxy_picker
from .service import ChainService
from .sysproxy import clear_system_proxy, is_system_proxy_set
from .tray import TrayIcon
from .vpn_detect import detect_vpn
from .windows_task import install_logon_task, task_exists, uninstall_logon_task
LOG_PATH = app_data_dir() / "proxy_chain_manager.log"
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
handlers=[
logging.FileHandler(LOG_PATH, encoding="utf-8"),
logging.StreamHandler(sys.stdout),
],
)
class UILogHandler(logging.Handler):
"""Forward ``proxy_chain_manager`` log records to the Live tab (via ``ui_q``). Thread-safe."""
def __init__(self, q: queue.Queue[dict[str, Any]]) -> None:
super().__init__(level=logging.DEBUG)
self._q = q
def emit(self, record: logging.LogRecord) -> None:
try:
text = self.format(record)
self._q.put_nowait({"type": "log", "text": text})
except Exception:
self.handleError(record)
# ── palette ──────────────────────────────────────────────────────────────────
BG = "#0d0d1a"
CARD = "#12122a"
PANEL = "#1a1a3e"
ACCENT = "#1e3a8a"
ACCENT2 = "#2563eb"
GREEN = "#00e676"
RED = "#ff1744"
YELLOW = "#ffab00"
ORANGE = "#ff6d00"
PURPLE = "#bb86fc"
BLUE = "#448aff"
CYAN = "#00bcd4"
DIM = "#4a5568"
TEXT = "#e2e8f0"
TEXT2 = "#94a3b8"
FONT = "Segoe UI"
def _ts() -> str:
return datetime.now().strftime("%H:%M:%S")
def main() -> None:
ctk.set_appearance_mode("dark")
ctk.set_default_color_theme("dark-blue")
root = ctk.CTk()
root.title("Proxy God v2")
root.geometry("1080x740")
root.minsize(900, 620)
root.configure(fg_color=BG)
ui_q: queue.Queue[dict[str, Any]] = queue.Queue()
svc = ChainService(notify=lambda m: ui_q.put(m))
s = load_settings()
browser = BrowserSession()
browser_should_run = [False]
browser_last_launch_ts = [0.0]
# ── tray ─────────────────────────────────────────────────────────────────
tray = TrayIcon(
on_show=lambda: root.after(0, lambda: (root.deiconify(), root.lift())),
on_quit=lambda: root.after(0, _close),
on_rotate=svc.rotate_now,
)
tray.start()
pulse_job: dict[str, str | None] = {"id": None}
HopState = Literal["untested", "testing", "ok", "fail"]
def _cancel_pulse() -> None:
if pulse_job["id"]:
try:
root.after_cancel(pulse_job["id"]) # type: ignore[arg-type]
except Exception:
pass
pulse_job["id"] = None
def _short(u: str) -> str:
u = redact_proxy_url(u)
u = u.replace("http://", "").replace("socks5://", "s5://").replace("socks4://", "s4://").replace("https://", "")
return u[:28] + "" if len(u) > 30 else u
# ─────────────────────────────────────────────────────────────────────────
# HELPERS
# ─────────────────────────────────────────────────────────────────────────
def _btn(parent: Any, label: str, cmd: Any, w: int = 80, h: int = 28, **kw: Any) -> ctk.CTkButton:
# Pull known keys out first so **kw never duplicates CTkButton parameters (font, width, …).
kw.pop("text", None)
kw.pop("command", None)
font = kw.pop("font", (FONT, 11))
fg_color = kw.pop("fg_color", ACCENT)
hover_color = kw.pop("hover_color", ACCENT2)
text_color = kw.pop("text_color", TEXT)
corner_radius = kw.pop("corner_radius", 6)
width = kw.pop("width", w)
height = kw.pop("height", h)
return ctk.CTkButton(
parent,
text=label,
command=cmd,
width=width,
height=height,
font=font,
fg_color=fg_color,
hover_color=hover_color,
text_color=text_color,
corner_radius=corner_radius,
**kw,
)
# ─────────────────────────────────────────────────────────────────────────
# TOP BAR
# ─────────────────────────────────────────────────────────────────────────
topbar = ctk.CTkFrame(root, fg_color=CARD, corner_radius=0, height=48)
topbar.pack(fill="x", side="top")
topbar.pack_propagate(False)
# Status
status_dot = ctk.CTkLabel(topbar, text="", font=(FONT, 20), text_color=RED)
status_dot.pack(side="left", padx=(10, 2))
status_txt = ctk.CTkLabel(topbar, text="STOPPED", font=(FONT, 12, "bold"), text_color=TEXT)
status_txt.pack(side="left", padx=(0, 8))
def _start() -> None:
_save_settings()
svc.start()
start_btn = _btn(topbar, "▶ Start", _start, w=86)
start_btn.pack(side="left", padx=2)
stop_btn = _btn(topbar, "■ Stop", svc.stop, w=76)
stop_btn.pack(side="left", padx=2)
rotate_btn = _btn(topbar, "↻ Rotate", svc.rotate_now, w=76)
rotate_btn.pack(side="left", padx=2)
_btn(topbar, "Quit", lambda: root.after(0, _close), w=52,
fg_color="#7f1d1d", hover_color="#991b1b").pack(side="left", padx=(10, 2))
# 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.pack(side="left", padx=2)
def _refresh_boot() -> None:
on = task_exists()
boot_lbl.configure(text="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}")
_refresh_boot()
def _rm_boot() -> None:
ok, msg = uninstall_logon_task()
_log("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)
# Right side indicators
rotation_lbl = ctk.CTkLabel(topbar, text="rot: 0", font=(FONT, 10), text_color=TEXT2)
rotation_lbl.pack(side="right", padx=(4, 12))
countdown_lbl = ctk.CTkLabel(topbar, text="next: —", font=(FONT, 10), text_color=TEXT2)
countdown_lbl.pack(side="right", padx=4)
proxy_lbl = ctk.CTkLabel(topbar, text=f"{LISTEN_HOST}:{s.local_port}",
font=(FONT, 11, "bold"), text_color=BLUE)
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.pack(side="right", padx=6)
sys_lbl = ctk.CTkLabel(topbar, text="SYS:—", font=(FONT, 10), text_color=DIM)
sys_lbl.pack(side="right", padx=4)
admin_badge = ctk.CTkLabel(
topbar,
text="⚡ ADMIN" if is_admin() else "👤 USER",
font=(FONT, 10, "bold"),
text_color=GREEN if is_admin() else YELLOW,
)
admin_badge.pack(side="right", padx=4)
if not is_admin():
def _elevate() -> None:
if request_admin_relaunch():
_close()
_btn(topbar, "Run as Admin", _elevate, w=104,
fg_color="#7f1d1d", hover_color="#991b1b").pack(side="right", padx=4)
# ─────────────────────────────────────────────────────────────────────────
# CHAIN VIZ BAR
# ─────────────────────────────────────────────────────────────────────────
viz_bar = ctk.CTkFrame(root, fg_color=PANEL, corner_radius=0, height=52)
viz_bar.pack(fill="x")
viz_bar.pack_propagate(False)
your_ip_lbl = ctk.CTkLabel(viz_bar, text="Your IP: —", font=(FONT, 10), text_color=TEXT2)
your_ip_lbl.pack(side="left", padx=(12, 8))
chain_canvas = ctk.CTkFrame(viz_bar, fg_color="transparent")
chain_canvas.pack(side="left", fill="both", expand=True)
exit_ip_lbl = ctk.CTkLabel(viz_bar, text="Exit: —",
font=(FONT, 13, "bold"), text_color=YELLOW)
exit_ip_lbl.pack(side="right", padx=(8, 14))
hop_dot_labels: list[ctk.CTkLabel] = []
outer_hop_label = "VPN" # updated when VPN detected; "Direct" if no tunnel
def _pulse(step: int = 0) -> None:
if not hop_dot_labels:
pulse_job["id"] = None
return
c = YELLOW if step % 2 == 0 else DIM
for d in hop_dot_labels:
d.configure(text_color=c)
pulse_job["id"] = root.after(380, lambda s=step: _pulse(s + 1)) # type: ignore[arg-type]
def _render_chain(
hops: list[str], status: str, exit_ip: str | None, fixed_last: bool = False
) -> None:
_cancel_pulse()
for w in list(chain_canvas.winfo_children()):
w.destroy()
hop_dot_labels.clear()
hop_c = {"healthy": GREEN, "dead": RED, "connecting": YELLOW}.get(status, DIM)
def _node(text: str, color: str, bold: bool = False) -> None:
ctk.CTkLabel(
chain_canvas, text=text,
font=(FONT, 11, "bold") if bold else (FONT, 10),
text_color=color,
).pack(side="left", padx=1)
def _arr() -> None:
ctk.CTkLabel(chain_canvas, text="", font=(FONT, 11), text_color=DIM).pack(side="left", padx=2)
_node("YOU", GREEN, bold=True)
_arr()
_node(outer_hop_label[:12], PURPLE, bold=True)
_arr()
last_i = len(hops) - 1
for i, h in enumerate(hops):
is_fixed = fixed_last and i == last_i and last_i >= 0
lbl_c = ORANGE if is_fixed else hop_c
dot = ctk.CTkLabel(chain_canvas, text="", font=(FONT, 12), text_color=lbl_c)
dot.pack(side="left", padx=1)
hop_dot_labels.append(dot)
label = _short(h) + ("" if is_fixed else "")
ctk.CTkLabel(chain_canvas, text=label, font=(FONT, 10), text_color=lbl_c).pack(side="left", padx=1)
if i < len(hops) - 1:
_arr()
_arr()
_node("WEB", BLUE, bold=True)
exit_ip_lbl.configure(
text=f"Exit: {exit_ip}" if exit_ip else "Exit: —",
text_color=GREEN if status == "healthy" else (RED if exit_ip else YELLOW),
)
if status == "connecting" and hop_dot_labels:
_pulse()
# ─────────────────────────────────────────────────────────────────────────
# PROGRESS STRIP
# ─────────────────────────────────────────────────────────────────────────
prog_strip = ctk.CTkFrame(root, fg_color=BG, height=22)
prog_strip.pack(fill="x", padx=8, pady=(4, 0))
prog_strip.pack_propagate(False)
phase_lbl = ctk.CTkLabel(prog_strip, text="idle", font=(FONT, 10), text_color=TEXT2, width=70)
phase_lbl.pack(side="left", padx=4)
prog_bar = ctk.CTkProgressBar(prog_strip, height=8, corner_radius=4,
fg_color=CARD, progress_color=BLUE)
prog_bar.pack(side="left", fill="x", expand=True, padx=4)
prog_bar.set(0)
pool_lbl = ctk.CTkLabel(prog_strip, text="pool: 0", font=(FONT, 10), text_color=TEXT2, width=70)
pool_lbl.pack(side="right", padx=4)
# ─────────────────────────────────────────────────────────────────────────
# TABVIEW
# ─────────────────────────────────────────────────────────────────────────
tabs = ctk.CTkTabview(
root,
fg_color=BG,
segmented_button_fg_color=CARD,
segmented_button_selected_color=ACCENT2,
segmented_button_unselected_color=CARD,
segmented_button_selected_hover_color=ACCENT,
border_width=0,
)
tabs.pack(fill="both", expand=True, padx=8, pady=(4, 8))
tab_live = tabs.add(" Live ")
tab_chain = tabs.add(" Chain Builder ")
tab_browser = tabs.add(" Browser ")
tab_privacy = tabs.add(" Privacy ")
tab_settings = tabs.add(" Settings ")
for tab in (tab_live, tab_chain, tab_browser, tab_privacy, tab_settings):
tab.configure(fg_color=BG)
# ─── TAB: LIVE ───────────────────────────────────────────────────────────
# Stats cards row
stats_row = ctk.CTkFrame(tab_live, fg_color="transparent")
stats_row.pack(fill="x", pady=(4, 6))
def _stat_card(parent: Any, title: str, var: ctk.StringVar, color: str = TEXT) -> None:
f = ctk.CTkFrame(parent, fg_color=CARD, corner_radius=8)
f.pack(side="left", fill="x", expand=True, padx=4)
ctk.CTkLabel(f, text=title, font=(FONT, 9), text_color=TEXT2).pack(
anchor="w", padx=8, pady=(6, 0))
ctk.CTkLabel(f, textvariable=var, font=(FONT, 14, "bold"), text_color=color).pack(
anchor="w", padx=8, pady=(0, 6))
v_real_ip = ctk.StringVar(value="")
v_exit_ip = ctk.StringVar(value="")
v_pool = ctk.StringVar(value="0")
v_rotation = ctk.StringVar(value="0")
_stat_card(stats_row, "Direct / VPN IP", v_real_ip)
_stat_card(stats_row, "Exit IP (last hop)", v_exit_ip)
_stat_card(stats_row, "Valid pool size", v_pool)
_stat_card(stats_row, "Rotations this session", v_rotation)
# Log box
log_frame = ctk.CTkFrame(tab_live, fg_color=CARD, corner_radius=8)
log_frame.pack(fill="both", expand=True)
log_box = ctk.CTkTextbox(
log_frame, font=("Consolas", 10),
fg_color=BG, text_color=TEXT,
scrollbar_button_color=ACCENT,
)
log_box.pack(fill="both", expand=True, padx=4, pady=(0, 4))
def _log(line: str) -> None:
log_box.insert("end", f"[{_ts()}] {line}\n")
log_box.see("end")
log_tool = ctk.CTkFrame(log_frame, fg_color="transparent")
log_tool.pack(fill="x", padx=8, pady=(6, 0), before=log_box)
verbose_logs_var = ctk.BooleanVar(value=True)
_pcm_logger = logging.getLogger("proxy_chain_manager")
_ui_log_handler: UILogHandler | None = None
for _h in _pcm_logger.handlers:
if isinstance(_h, UILogHandler):
_ui_log_handler = _h
break
if _ui_log_handler is None:
_ui_log_handler = UILogHandler(ui_q)
_ui_log_handler.setFormatter(
logging.Formatter("%(levelname)-5s [%(name)s] %(message)s")
)
_pcm_logger.addHandler(_ui_log_handler)
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("httpcore").setLevel(logging.WARNING)
def _sync_ui_log_level() -> None:
on = bool(verbose_logs_var.get())
lvl = logging.DEBUG if on else logging.INFO
_pcm_logger.setLevel(lvl)
if _ui_log_handler is not None:
_ui_log_handler.setLevel(lvl)
_log(f"UI log level: {'DEBUG (verbose)' if on else 'INFO'}")
ctk.CTkCheckBox(
log_tool,
text="Verbose logs (DEBUG in UI — file stays INFO)",
variable=verbose_logs_var,
command=_sync_ui_log_level,
font=(FONT, 11),
fg_color=ACCENT2,
hover_color=ACCENT,
text_color=TEXT,
).pack(side="left", padx=(0, 12))
_btn(log_tool, "Clear log", lambda: log_box.delete("1.0", "end"), w=88, h=26,
fg_color=DIM, hover_color=ACCENT).pack(side="left", padx=(0, 6))
_sync_ui_log_level()
# ─── TAB: CHAIN BUILDER ──────────────────────────────────────────────────
cb_split = ctk.CTkFrame(tab_chain, fg_color="transparent")
cb_split.pack(fill="x", pady=(4, 8))
# Left — mode + hop slider
cb_left = ctk.CTkFrame(cb_split, fg_color=CARD, corner_radius=8)
cb_left.pack(side="left", fill="both", expand=True, padx=(0, 6))
ctk.CTkLabel(cb_left, text="Obfuscation Mode", font=(FONT, 12, "bold"),
text_color=TEXT).pack(anchor="w", padx=12, pady=(12, 4))
mode_var = ctk.StringVar(value=s.obfuscation_mode)
for mk in OBFUSCATION_MODES:
ctk.CTkRadioButton(
cb_left,
text=OBFUSCATION_LABELS[mk],
variable=mode_var,
value=mk,
font=(FONT, 11),
fg_color=ACCENT2,
hover_color=ACCENT,
text_color=TEXT,
).pack(anchor="w", padx=16, pady=3)
elite_var = ctk.BooleanVar(value=s.prefer_elite)
ctk.CTkCheckBox(
cb_left, text="Elite proxies only (slower but more anonymous)",
variable=elite_var, font=(FONT, 11),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT,
).pack(anchor="w", padx=16, pady=(0, 8))
exit_fix_frame = ctk.CTkFrame(cb_left, fg_color=PANEL, corner_radius=8)
exit_fix_frame.pack(fill="x", padx=12, pady=(0, 12))
exit_hdr = ctk.CTkFrame(exit_fix_frame, fg_color="transparent")
exit_hdr.pack(fill="x", padx=10, pady=(8, 2))
ctk.CTkLabel(
exit_hdr,
text="Exit node (optional final hop)",
font=(FONT, 11, "bold"),
text_color=ORANGE,
).pack(side="left")
exit_test_dot = ctk.CTkLabel(exit_hdr, text="", font=(FONT, 14), text_color=RED)
exit_test_dot.pack(side="left", padx=(8, 4))
exit_proxy_entry = ctk.CTkEntry(
exit_fix_frame,
placeholder_text="host:port:user:pass (defaults to socks5 — paste any format)",
font=("Consolas", 10),
fg_color=BG,
border_color=ACCENT,
height=28,
)
exit_proxy_entry.pack(fill="x", padx=10, pady=(0, 4))
exit_auth_row = ctk.CTkFrame(exit_fix_frame, fg_color="transparent")
exit_auth_row.pack(fill="x", padx=10, pady=(0, 4))
ctk.CTkLabel(exit_auth_row, text="User", font=(FONT, 10), text_color=TEXT2, width=36).pack(
side="left", padx=(0, 4)
)
exit_user_entry = ctk.CTkEntry(
exit_auth_row, width=120, height=26, font=("Consolas", 10),
fg_color=BG, border_color=ACCENT, placeholder_text="optional",
)
exit_user_entry.pack(side="left", padx=(0, 10))
ctk.CTkLabel(exit_auth_row, text="Pass", font=(FONT, 10), text_color=TEXT2, width=36).pack(
side="left", padx=(0, 4)
)
exit_pass_entry = ctk.CTkEntry(
exit_auth_row, width=120, height=26, font=("Consolas", 10),
fg_color=BG, border_color=ACCENT, placeholder_text="optional", show="*",
)
exit_pass_entry.pack(side="left", padx=(0, 0))
_eb, _eu, _epw = split_proxy_for_edit(s.manual_exit_proxy)
if _eb:
exit_proxy_entry.insert(0, _eb)
if _eu:
exit_user_entry.insert(0, _eu)
if _epw:
exit_pass_entry.insert(0, _epw)
exit_btn_row = ctk.CTkFrame(exit_fix_frame, fg_color="transparent")
exit_btn_row.pack(fill="x", padx=10, pady=(0, 4))
def _auto_split_exit_paste(_event: Any | None = None) -> None:
"""If the URL field looks like ``host:port:user:pass``, distribute
the user/pass parts into their own fields so the user sees what got
parsed. Triggered on key release and explicit paste.
"""
raw = exit_proxy_entry.get()
if "://" in raw:
return
bits = [b for b in raw.strip().split(":") if b != ""]
if len(bits) < 4 and "@" not in raw and not any(c in raw for c in (" ", "\t")):
return
base, user, pw = parse_exit_proxy_input(raw, default_scheme="socks5")
if not base:
return
if base != raw:
exit_proxy_entry.delete(0, "end")
exit_proxy_entry.insert(0, base)
if user and not exit_user_entry.get().strip():
exit_user_entry.delete(0, "end")
exit_user_entry.insert(0, user)
if pw and not exit_pass_entry.get().strip():
exit_pass_entry.delete(0, "end")
exit_pass_entry.insert(0, pw)
exit_proxy_entry.bind("<KeyRelease>", _auto_split_exit_paste)
exit_proxy_entry.bind("<<Paste>>", lambda e: exit_proxy_entry.after(1, _auto_split_exit_paste))
exit_proxy_entry.bind("<FocusOut>", _auto_split_exit_paste)
def _exit_url() -> str:
"""Build the final exit-proxy URL.
Accepts every paste format ``parse_exit_proxy_input`` understands
(host:port:user:pass, user:pass@host:port, scheme://…). Defaults to
SOCKS5 when no scheme is supplied.
"""
base, parsed_user, parsed_pw = parse_exit_proxy_input(
exit_proxy_entry.get(), default_scheme="socks5"
)
if not base:
return ""
user = (exit_user_entry.get() or "").strip() or parsed_user
pw = (exit_pass_entry.get() or "").strip() or parsed_pw
return merge_proxy_credentials(base, user, pw)
def _test_exit() -> None:
url = _exit_url()
if not url:
_log("Exit node: enter a proxy URL first.")
return
exit_test_dot.configure(text_color=YELLOW)
test_exit_btn.configure(state="disabled")
def work() -> None:
def on_result(_u: str, ok: bool) -> None:
root.after(0, lambda: exit_test_dot.configure(text_color=GREEN if ok else RED))
root.after(0, lambda: _log(
f"Exit node: {'OK' if ok else 'FAILED'}{_short(url)}"
))
run_validate_each_sync(
[url],
svc.settings.ip_check_url,
min(12.0, svc.settings.validation_timeout_seconds),
on_result,
concurrency=1,
)
root.after(0, lambda: test_exit_btn.configure(state="normal"))
threading.Thread(target=work, daemon=True).start()
test_exit_btn = _btn(exit_btn_row, "Test exit", _test_exit, w=88, h=26,
fg_color=ACCENT2, hover_color=ACCENT)
test_exit_btn.pack(side="left")
ctk.CTkLabel(
exit_fix_frame,
text=(
"Appended after your chain hops. Leave empty to use last chain hop as exit.\n"
"Paste any of: host:port:user:pass • user:pass@host:port • scheme://host:port\n"
"No scheme = SOCKS5. Paste auto-splits into User/Pass below."
),
font=(FONT, 9),
text_color=TEXT2,
justify="left",
).pack(anchor="w", padx=10, pady=(0, 8))
# Right — your chain
cb_right = ctk.CTkFrame(cb_split, fg_color=CARD, corner_radius=8, width=400)
cb_right.pack(side="left", fill="both", expand=True)
hdr_row = ctk.CTkFrame(cb_right, fg_color="transparent")
hdr_row.pack(fill="x", padx=12, pady=(12, 2))
ctk.CTkLabel(
hdr_row, text="Your chain",
font=(FONT, 12, "bold"), text_color=TEXT,
).pack(side="left")
chain_count_lbl = ctk.CTkLabel(hdr_row, text="", font=(FONT, 10), text_color=TEXT2)
chain_count_lbl.pack(side="left", padx=(8, 0))
use_manual_var = ctk.BooleanVar(value=True if s.pinned_chain else s.use_pinned_chain)
use_chain_cb = ctk.CTkCheckBox(
hdr_row, text="Use this chain on Start",
variable=use_manual_var, font=(FONT, 10),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT2,
)
use_chain_cb.pack(side="right")
ctk.CTkLabel(
cb_right,
text="Traffic flows top → bottom. ● red = not tested ● green = live",
font=(FONT, 9), text_color=TEXT2,
).pack(anchor="w", padx=12, pady=(0, 4))
manual_chain: list[str] = list(s.pinned_chain)
hop_status: dict[str, HopState] = {u: "untested" for u in manual_chain}
status_dots: dict[str, ctk.CTkLabel] = {}
_HOP_COLOR = {"untested": RED, "testing": YELLOW, "ok": GREEN, "fail": RED}
chain_list_frame = ctk.CTkScrollableFrame(
cb_right, fg_color=BG, height=200, corner_radius=6,
scrollbar_button_color=ACCENT,
)
chain_list_frame.pack(fill="x", padx=12, pady=(0, 4))
def _update_chain_count() -> None:
n = len(manual_chain)
chain_count_lbl.configure(text=f"({n} hop{'s' if n != 1 else ''})")
def _rebuild_chain_ui() -> None:
status_dots.clear()
for w in list(chain_list_frame.winfo_children()):
w.destroy()
for i, hop in enumerate(manual_chain):
if hop not in hop_status:
hop_status[hop] = "untested"
_chain_row(i, hop)
_update_chain_count()
def _chain_row(idx: int, hop: str) -> None:
fr = ctk.CTkFrame(chain_list_frame, fg_color=PANEL, corner_radius=6, height=32)
fr.pack(fill="x", pady=2)
fr.pack_propagate(False)
st = hop_status.get(hop, "untested")
dot = ctk.CTkLabel(fr, text="", font=(FONT, 12),
text_color=_HOP_COLOR[st], width=18)
dot.pack(side="left", padx=(4, 0))
status_dots[hop] = dot
ctk.CTkLabel(fr, text=f"{idx+1}", font=(FONT, 10, "bold"),
text_color=CYAN, width=22).pack(side="left")
proto = hop.split("://")[0].upper() if "://" in hop else "HTTP"
badge_c = {"HTTP": "#1e3a8a", "SOCKS5": "#064e3b", "SOCKS4": "#3b1a64"}.get(proto, DIM)
ctk.CTkLabel(fr, text=proto, font=(FONT, 9, "bold"),
fg_color=badge_c, corner_radius=4, padx=4,
text_color=TEXT).pack(side="left", padx=2)
ctk.CTkLabel(fr, text=_short(hop), font=("Consolas", 10),
text_color=TEXT, anchor="w").pack(side="left", fill="x", expand=True, padx=4)
def _up(i: int = idx) -> None:
if i > 0:
manual_chain[i], manual_chain[i-1] = manual_chain[i-1], manual_chain[i]
_rebuild_chain_ui()
def _dn(i: int = idx) -> None:
if i < len(manual_chain) - 1:
manual_chain[i], manual_chain[i+1] = manual_chain[i+1], manual_chain[i]
_rebuild_chain_ui()
def _rm(i: int = idx) -> None:
removed = manual_chain[i]
del manual_chain[i]
hop_status.pop(removed, None)
_rebuild_chain_ui()
_btn(fr, "", _up, w=26, h=24, fg_color=DIM, hover_color=ACCENT).pack(side="right", padx=1)
_btn(fr, "", _dn, w=26, h=24, fg_color=DIM, hover_color=ACCENT).pack(side="right", padx=1)
_btn(fr, "", _rm, w=26, h=24, fg_color="#7f1d1d", hover_color=RED).pack(side="right", padx=(1, 6))
_rebuild_chain_ui()
test_row = ctk.CTkFrame(cb_right, fg_color="transparent")
test_row.pack(fill="x", padx=12, pady=(0, 6))
def _set_all_status(state: HopState) -> None:
for u in manual_chain:
hop_status[u] = state
if u in status_dots:
status_dots[u].configure(text_color=_HOP_COLOR[state])
def _test_chain() -> None:
if not manual_chain:
_log("Add proxies to your chain first.")
return
test_chain_btn.configure(state="disabled")
_set_all_status("testing")
check_url = svc.settings.ip_check_url
timeout = min(12.0, svc.settings.validation_timeout_seconds)
ok_count = [0]
def on_result(url: str, ok: bool) -> None:
hop_status[url] = "ok" if ok else "fail"
if ok:
ok_count[0] += 1
def ui() -> None:
if url in status_dots:
status_dots[url].configure(text_color=GREEN if ok else RED)
root.after(0, ui)
def work() -> None:
run_validate_each_sync(manual_chain, check_url, timeout, on_result)
n_ok, n_tot = ok_count[0], len(manual_chain)
def done() -> None:
test_chain_btn.configure(state="normal")
_log(f"Chain test: {n_ok}/{n_tot} proxies responded OK.")
root.after(0, done)
threading.Thread(target=work, daemon=True).start()
test_chain_btn = _btn(test_row, "▶ Test entire chain", _test_chain, w=150, h=28,
fg_color=ACCENT2, hover_color=ACCENT)
test_chain_btn.pack(side="left", padx=(0, 8))
# Add proxy input
add_row = ctk.CTkFrame(cb_right, fg_color="transparent")
add_row.pack(fill="x", padx=12, pady=(0, 4))
add_entry = ctk.CTkEntry(
add_row,
placeholder_text="http://host:port or user:pass@host:port",
font=(FONT, 10), fg_color=BG, border_color=ACCENT, height=28,
)
add_entry.pack(side="left", fill="x", expand=True, padx=(0, 4))
add_user_entry = ctk.CTkEntry(
add_row, width=88, height=28, font=("Consolas", 10),
fg_color=BG, border_color=ACCENT, placeholder_text="user",
)
add_user_entry.pack(side="left", padx=(0, 2))
add_pass_entry = ctk.CTkEntry(
add_row, width=88, height=28, font=("Consolas", 10),
fg_color=BG, border_color=ACCENT, placeholder_text="pass", show="*",
)
add_pass_entry.pack(side="left", padx=(0, 4))
def _add_proxy() -> None:
v = add_entry.get().strip()
if not v:
return
if "://" not in v:
v = "http://" + v
au = add_user_entry.get().strip()
ap = add_pass_entry.get().strip()
if au or ap:
v = merge_proxy_credentials(v, au, ap)
else:
v = normalize_proxy_url(v)
manual_chain.append(v)
hop_status[v] = "untested"
_rebuild_chain_ui()
add_entry.delete(0, "end")
add_user_entry.delete(0, "end")
add_pass_entry.delete(0, "end")
_btn(add_row, "+ Add", _add_proxy, w=68, h=28).pack(side="left")
chain_btn_row = ctk.CTkFrame(cb_right, fg_color="transparent")
chain_btn_row.pack(pady=(0, 10))
def _on_picker_done(urls: list[str], mode: str) -> None:
if mode == "replace":
manual_chain.clear()
hop_status.clear()
for u in urls:
if u not in manual_chain:
manual_chain.append(u)
hop_status[u] = "untested"
_rebuild_chain_ui()
_log(f"{'Replaced' if mode == 'replace' else 'Added'} {len(urls)} proxies to your chain.")
def _open_picker() -> None:
_save_settings()
show_proxy_picker(root, svc.settings, bool(elite_var.get()), _on_picker_done)
browse_btn = _btn(chain_btn_row, "Browse proxy lists…", _open_picker, w=150, h=26,
fg_color=ACCENT2, hover_color=ACCENT)
browse_btn.pack(side="left")
# Sources
src_frame = ctk.CTkFrame(tab_chain, fg_color=CARD, corner_radius=8)
src_frame.pack(fill="x", pady=(0, 6))
ctk.CTkLabel(src_frame, text="Proxy Sources (one JSON URL per line)",
font=(FONT, 11, "bold"), text_color=TEXT).pack(
anchor="w", padx=12, pady=(8, 4))
sources_box = ctk.CTkTextbox(
src_frame, height=72, font=("Consolas", 10),
fg_color=BG, text_color=TEXT, scrollbar_button_color=ACCENT,
)
sources_box.pack(fill="x", padx=12, pady=(0, 8))
sources_box.insert("end", "\n".join(s.sources))
# ─── TAB: BROWSER ─────────────────────────────────────────────────────────
br_scroll = ctk.CTkScrollableFrame(tab_browser, fg_color=BG, scrollbar_button_color=ACCENT)
br_scroll.pack(fill="both", expand=True, padx=4, pady=4)
def _br_section(title: str, subtitle: str = "") -> ctk.CTkFrame:
ctk.CTkLabel(br_scroll, text=title, font=(FONT, 13, "bold"), text_color=CYAN).pack(
anchor="w", padx=8, pady=(12, 0)
)
if subtitle:
ctk.CTkLabel(
br_scroll, text=subtitle, font=(FONT, 10), text_color=TEXT2, wraplength=900, justify="left"
).pack(anchor="w", padx=8, pady=(0, 4))
f = ctk.CTkFrame(br_scroll, fg_color=CARD, corner_radius=8)
f.pack(fill="x", padx=8, pady=(4, 8))
return f
browser_status_card = _br_section(
"Hardened Firefox",
"Launches Firefox in an app-controlled profile that enforces proxy/hardening prefs on every launch.",
)
browser_status_lbl = ctk.CTkLabel(
browser_status_card, text="Browser status: stopped", font=(FONT, 12, "bold"), text_color=TEXT2
)
browser_status_lbl.pack(anchor="w", padx=12, pady=(10, 2))
browser_hint_lbl = ctk.CTkLabel(
browser_status_card, text="Exit IP on hover in tray while chain is healthy.", font=(FONT, 10), text_color=TEXT2
)
browser_hint_lbl.pack(anchor="w", padx=12, pady=(0, 10))
browser_path_card = _br_section("Executable and profile", "Set your Firefox path once; profile is kept isolated.")
path_row = ctk.CTkFrame(browser_path_card, fg_color="transparent")
path_row.pack(fill="x", padx=12, pady=(10, 4))
ctk.CTkLabel(path_row, text="Firefox exe", width=90, anchor="w", font=(FONT, 10), text_color=TEXT2).pack(side="left")
firefox_path_var = ctk.StringVar(value=s.firefox_path or default_firefox_path())
firefox_path_entry = ctk.CTkEntry(path_row, textvariable=firefox_path_var, height=28, fg_color=BG, border_color=ACCENT)
firefox_path_entry.pack(side="left", fill="x", expand=True, padx=(0, 6))
def _pick_firefox_path() -> None:
p = filedialog.askopenfilename(
title="Select firefox.exe",
filetypes=[("Firefox", "firefox.exe"), ("Executable", "*.exe"), ("All files", "*.*")],
)
if p:
firefox_path_var.set(p)
_btn(path_row, "Browse", _pick_firefox_path, w=72, h=28, fg_color=DIM, hover_color=ACCENT).pack(side="left")
profile_row = ctk.CTkFrame(browser_path_card, fg_color="transparent")
profile_row.pack(fill="x", padx=12, pady=(0, 10))
ctk.CTkLabel(profile_row, text="Profile dir", width=90, anchor="w", font=(FONT, 10), text_color=TEXT2).pack(side="left")
firefox_profile_var = ctk.StringVar(value=s.firefox_profile_dir or default_profile_dir())
firefox_profile_entry = ctk.CTkEntry(
profile_row, textvariable=firefox_profile_var, height=28, fg_color=BG, border_color=ACCENT
)
firefox_profile_entry.pack(side="left", fill="x", expand=True)
browser_toggles_card = _br_section(
"Browser controls",
"These settings are applied each time you launch from this tab.",
)
br_force_proxy_var = ctk.BooleanVar(value=s.browser_force_proxy)
br_disable_webrtc_var = ctk.BooleanVar(value=s.browser_disable_webrtc)
br_rfp_var = ctk.BooleanVar(value=s.browser_resist_fingerprinting)
br_telemetry_var = ctk.BooleanVar(value=s.browser_disable_telemetry)
br_fpi_var = ctk.BooleanVar(value=s.browser_first_party_isolation)
br_strict_tp_var = ctk.BooleanVar(value=s.browser_strict_tracking_protection)
br_tz_utc_var = ctk.BooleanVar(value=s.browser_timezone_utc)
br_clear_var = ctk.BooleanVar(value=s.browser_clear_on_close)
br_disposable_var = ctk.BooleanVar(value=s.browser_disposable_profile)
br_kill_drop_var = ctk.BooleanVar(value=s.browser_kill_on_chain_drop)
br_auto_relaunch_var = ctk.BooleanVar(value=s.browser_auto_relaunch)
br_lock_profile_var = ctk.BooleanVar(value=s.browser_lock_managed_profile)
def _br_toggle(parent: Any, text: str, var: ctk.BooleanVar, tip: str = "") -> ctk.CTkCheckBox:
cb = ctk.CTkCheckBox(
parent, text=text, variable=var, font=(FONT, 11),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT,
)
cb.pack(anchor="w", padx=12, pady=5)
return cb
_br_toggle(
browser_toggles_card, "Force browser proxy to local chain", br_force_proxy_var,
"Forces Firefox traffic through Proxy God listener. Prevents direct bypass.",
)
_br_toggle(
browser_toggles_card, "Disable WebRTC peer connections", br_disable_webrtc_var,
"Stops common WebRTC local-IP leaks in browser calls.",
)
_br_toggle(
browser_toggles_card, "Enable Resist Fingerprinting", br_rfp_var,
"Turns on Firefox anti-fingerprinting mode (slightly changes site behavior).",
)
_br_toggle(
browser_toggles_card, "Disable telemetry and studies", br_telemetry_var,
"Disables browser diagnostics/study pings.",
)
_br_toggle(
browser_toggles_card, "Enable First-Party Isolation", br_fpi_var,
"Separates site storage per domain to reduce cross-site tracking.",
)
_br_toggle(
browser_toggles_card, "Strict tracking protection", br_strict_tp_var,
"Uses stronger tracker/cookie blocking in Firefox.",
)
_br_toggle(
browser_toggles_card, "Timezone hardening (UTC-friendly timer precision)", br_tz_utc_var,
"Reduces timing precision used in fingerprinting.",
)
_br_toggle(
browser_toggles_card, "Clear cookies/cache/history on close", br_clear_var,
"Wipes session traces from the managed profile when browser closes.",
)
_br_toggle(
browser_toggles_card, "Disposable profile (delete on close)", br_disposable_var,
"Deletes the entire managed profile folder on stop/close.",
)
_br_toggle(
browser_toggles_card, "Kill browser if chain drops", br_kill_drop_var,
"Stops browser immediately if chain becomes unhealthy.",
)
_br_toggle(
browser_toggles_card, "Auto-relaunch browser while chain is healthy", br_auto_relaunch_var,
"If browser exits unexpectedly, relaunch it automatically while chain stays up.",
)
_br_toggle(
browser_toggles_card, "Lock to managed profile (recommended)", br_lock_profile_var,
"Launches Firefox only with Proxy God profile and re-applies settings each launch.",
)
browser_action_row = ctk.CTkFrame(br_scroll, fg_color="transparent")
browser_action_row.pack(fill="x", padx=8, pady=(2, 14))
def _browser_cfg() -> BrowserConfig:
return BrowserConfig(
firefox_path=firefox_path_var.get().strip(),
profile_dir=firefox_profile_var.get().strip(),
clear_on_close=bool(br_clear_var.get()),
disposable_profile=bool(br_disposable_var.get()),
kill_on_chain_drop=bool(br_kill_drop_var.get()),
auto_relaunch=bool(br_auto_relaunch_var.get()),
lock_managed_profile=bool(br_lock_profile_var.get()),
force_proxy=bool(br_force_proxy_var.get()),
disable_webrtc=bool(br_disable_webrtc_var.get()),
resist_fingerprinting=bool(br_rfp_var.get()),
disable_telemetry=bool(br_telemetry_var.get()),
first_party_isolation=bool(br_fpi_var.get()),
strict_tracking_protection=bool(br_strict_tp_var.get()),
timezone_utc=bool(br_tz_utc_var.get()),
)
def _refresh_browser_status() -> None:
if browser.is_running():
browser_status_lbl.configure(
text=f"Browser status: running (pid {browser.pid()})",
text_color=GREEN,
)
else:
if browser_should_run[0]:
browser_status_lbl.configure(text="Browser status: waiting/restarting", text_color=YELLOW)
else:
browser_status_lbl.configure(text="Browser status: stopped", text_color=TEXT2)
def _launch_browser_with_cfg(cfg: BrowserConfig) -> bool:
if not svc.current_chain:
_log("Chain not ready yet — starting chain and launching browser now.")
_start()
ok, msg = browser.launch(cfg, LISTEN_HOST, svc.settings.local_port)
_log(msg)
_refresh_browser_status()
return bool(ok)
def _launch_browser() -> None:
_save_settings()
cfg = _browser_cfg()
browser_should_run[0] = True
if _launch_browser_with_cfg(cfg):
browser_last_launch_ts[0] = time.monotonic()
def _stop_browser() -> None:
cfg = _browser_cfg()
browser_should_run[0] = False
ok, msg = browser.stop(dispose=cfg.disposable_profile)
_log(msg)
_refresh_browser_status()
launch_browser_btn = _btn(
browser_action_row, "Launch Hardened Firefox", _launch_browser, w=190, h=34, font=(FONT, 12)
)
launch_browser_btn.pack(side="left", padx=(0, 8))
stop_browser_btn = _btn(
browser_action_row, "Stop Browser", _stop_browser, w=110, h=34,
fg_color="#7f1d1d", hover_color="#991b1b",
)
stop_browser_btn.pack(side="left")
# ─── TAB: PRIVACY ──────────────────────────────────────────────────────────
priv_scroll = ctk.CTkScrollableFrame(tab_privacy, fg_color=BG,
scrollbar_button_color=ACCENT)
priv_scroll.pack(fill="both", expand=True, padx=4, pady=4)
def _priv_section(title: str, subtitle: str = "") -> ctk.CTkFrame:
ctk.CTkLabel(priv_scroll, text=title, font=(FONT, 13, "bold"),
text_color=CYAN).pack(anchor="w", padx=8, pady=(12, 0))
if subtitle:
ctk.CTkLabel(priv_scroll, text=subtitle, font=(FONT, 10),
text_color=TEXT2, wraplength=900, justify="left").pack(
anchor="w", padx=8, pady=(0, 4))
f = ctk.CTkFrame(priv_scroll, fg_color=CARD, corner_radius=8)
f.pack(fill="x", padx=8, pady=(4, 8))
return f
status_card = _priv_section(
"Network identity",
"VPN detection drives leak checks: strict without VPN, subnet-aware with VPN.",
)
status_row = ctk.CTkFrame(status_card, fg_color="transparent")
status_row.pack(fill="x", padx=12, pady=10)
vpn_status_lbl = ctk.CTkLabel(status_row, text="VPN: scanning…",
font=(FONT, 12, "bold"), text_color=TEXT)
vpn_status_lbl.pack(anchor="w")
leak_mode_lbl = ctk.CTkLabel(status_row, text="Leak check: —",
font=(FONT, 11), text_color=TEXT2)
leak_mode_lbl.pack(anchor="w", pady=(4, 0))
def _refresh_vpn_ui() -> None:
vs = detect_vpn()
nonlocal outer_hop_label
if vs.active:
outer_hop_label = vs.short_label()[:12]
vpn_status_lbl.configure(
text=f"VPN: {vs.label}" + (f" ({vs.adapter})" if vs.adapter else ""),
text_color=GREEN,
)
leak_mode_lbl.configure(text="Leak check: VPN-aware (/16 subnet)")
else:
outer_hop_label = "Direct"
vpn_status_lbl.configure(text="VPN: not detected (direct connection)",
text_color=YELLOW)
leak_mode_lbl.configure(text="Leak check: strict (exit must differ from your IP)")
_btn(status_card, "Refresh VPN status", _refresh_vpn_ui, w=140, h=28).pack(
anchor="w", padx=12, pady=(0, 10))
toggles_card = _priv_section(
"Protections while chain is running",
"Applied on Start, restored on Stop. Admin required for MAC, hostname, IPv6, and WebRTC.",
)
mac_var = ctk.BooleanVar(value=s.mac_spoof_enabled)
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)
def _priv_toggle(parent: Any, text: str, var: ctk.BooleanVar, tip: str = "") -> ctk.CTkCheckBox:
cb = ctk.CTkCheckBox(
parent, text=text, variable=var, font=(FONT, 11),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT,
)
cb.pack(anchor="w", padx=12, pady=6)
return cb
_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, "Spoof computer / NetBIOS hostname", host_var,
"Temporarily renames machine identity while active (admin required).",
)
_priv_toggle(
toggles_card, "Flush DNS cache when starting or rotating chains", dns_flush_var,
"Clears cached resolver results to reduce stale direct routes.",
)
_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.",
)
fp_card = _priv_section(
"Device fingerprint",
"OS-level identifiers. Browsers still fingerprint Canvas, fonts, and WebGL separately.",
)
fp_box = ctk.CTkTextbox(fp_card, height=140, font=("Consolas", 10),
fg_color=BG, text_color=TEXT,
scrollbar_button_color=ACCENT)
fp_box.pack(fill="x", padx=12, pady=(4, 8))
def _refresh_fingerprint() -> None:
fp_box.delete("1.0", "end")
audit = audit_device()
fp_box.insert("end", "\n".join(audit.lines))
_btn(fp_card, "Refresh fingerprint audit", _refresh_fingerprint, w=180, h=28).pack(
anchor="w", padx=12, pady=(0, 10))
dns_card = _priv_section("DNS", "Checks whether public resolvers may bypass your chain.")
dns_result_lbl = ctk.CTkLabel(dns_card, text="Run a check to see DNS configuration.",
font=(FONT, 11), text_color=TEXT2, wraplength=880, justify="left")
dns_result_lbl.pack(anchor="w", padx=12, pady=8)
def _run_dns_check() -> None:
listen = f"http://{svc.settings.listen_addr()}" if svc.current_chain else None
r = check_dns_leak_hint(listen)
color = GREEN if r.ok else RED
dns_result_lbl.configure(
text=r.message + (f"\nResolvers: {', '.join(r.system_resolvers)}" if r.system_resolvers else ""),
text_color=color,
)
dns_btn_row = ctk.CTkFrame(dns_card, fg_color="transparent")
dns_btn_row.pack(fill="x", padx=12, pady=(0, 10))
_btn(dns_btn_row, "DNS leak check", _run_dns_check, w=120, h=28).pack(side="left", padx=(0, 8))
def _manual_dns_flush() -> None:
ok, msg = flush_dns_cache()
dns_result_lbl.configure(
text=msg, text_color=GREEN if ok else RED,
)
_btn(dns_btn_row, "Flush DNS now", _manual_dns_flush, w=110, h=28,
fg_color=DIM).pack(side="left")
_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)
# ─── TAB: SETTINGS ───────────────────────────────────────────────────────
sf_outer = ctk.CTkScrollableFrame(tab_settings, fg_color=BG,
scrollbar_button_color=ACCENT)
sf_outer.pack(fill="both", expand=True)
def _section(title: str) -> ctk.CTkFrame:
ctk.CTkLabel(sf_outer, text=title, font=(FONT, 12, "bold"),
text_color=CYAN).pack(anchor="w", padx=6, pady=(14, 4))
f = ctk.CTkFrame(sf_outer, fg_color=CARD, corner_radius=8)
f.pack(fill="x", padx=6, pady=(0, 4))
return f
entries: dict[str, ctk.CTkEntry] = {}
def _fld(parent: Any, label: str, key: str, val: str, tip: str = "") -> None:
row = ctk.CTkFrame(parent, fg_color="transparent")
row.pack(fill="x", padx=12, pady=5)
ctk.CTkLabel(row, text=label, font=(FONT, 11), text_color=TEXT,
width=230, anchor="w").pack(side="left")
e = ctk.CTkEntry(row, height=26, font=(FONT, 11),
fg_color=BG, border_color=ACCENT, width=130)
e.insert(0, val)
e.pack(side="left")
if tip:
ctk.CTkLabel(row, text=f" {tip}", font=(FONT, 10),
text_color=TEXT2).pack(side="left")
entries[key] = e
net = _section("Network")
_fld(net, "Local listener port", "port", str(s.local_port), "(point apps here)")
_fld(net, "Proxy bypass list", "bypass", s.proxy_bypass, "(semicolons)")
_fld(net, "IP check URL", "check_url", s.ip_check_url)
timing = _section("Timing")
_fld(timing, "Health check interval (sec)", "health", str(s.health_check_seconds),
"(how often exit IP is re-verified)")
_fld(timing, "Full pool refresh (sec)", "refresh", str(s.full_refresh_seconds),
"(re-fetch + re-validate)")
val_sec = _section("Validation")
_fld(val_sec, "Concurrent validators", "conc", str(s.validation_concurrency))
_fld(val_sec, "Stop after N valid proxies found", "minpool", str(s.min_pool_size),
"(early-exit — stop testing once enough found)")
_fld(val_sec, "Max candidates to test per cycle", "maxc", str(s.max_candidates),
"(shuffled random sample from fetch)")
_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")
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)
dns_flush_var.set(True)
mac_var.set(False)
host_var.set(False)
ipv6_var.set(False)
webrtc_var.set(True)
br_force_proxy_var.set(True)
br_disable_webrtc_var.set(True)
br_rfp_var.set(True)
br_telemetry_var.set(True)
br_fpi_var.set(True)
br_strict_tp_var.set(True)
br_tz_utc_var.set(True)
br_clear_var.set(True)
br_disposable_var.set(False)
br_kill_drop_var.set(True)
br_auto_relaunch_var.set(False)
br_lock_profile_var.set(True)
if not firefox_path_var.get().strip():
firefox_path_var.set(default_firefox_path())
if not firefox_profile_var.get().strip():
firefox_profile_var.set(default_profile_dir())
_save_settings(verbose=False)
_log("⚡ Point-and-shoot preset applied.")
ready_btn = _btn(
topbar,
"⚡ Ready",
_apply_point_and_shoot,
w=78,
fg_color="#0f766e",
hover_color="#0d9488",
)
ready_btn.pack(side="left", padx=(8, 2))
ctk.CTkFrame(sf_outer, fg_color="transparent", height=4).pack()
_btn(sf_outer, "Save All Settings",
lambda: _save_settings(verbose=True), w=220, h=36,
font=(FONT, 13)).pack(anchor="w", padx=6, pady=8)
# ─────────────────────────────────────────────────────────────────────────
# SAVE SETTINGS
# ─────────────────────────────────────────────────────────────────────────
def _save_settings(verbose: bool = False) -> None:
try:
src_raw = sources_box.get("1.0", "end").strip()
src_list = [ln.strip() for ln in src_raw.splitlines()
if ln.strip().startswith("http")]
n_hops = max(1, len(manual_chain)) if manual_chain else 1
ns = Settings(
local_host=LISTEN_HOST,
local_port=int(entries["port"].get().strip()),
chain_length=n_hops,
obfuscation_mode=mode_var.get(),
use_pinned_chain=bool(use_manual_var.get()),
pinned_chain=list(manual_chain),
manual_exit_proxy=_exit_url(),
health_check_seconds=min(3600, max(10, int(entries["health"].get().strip()))),
full_refresh_seconds=min(86400, max(60, int(entries["refresh"].get().strip()))),
validation_concurrency=max(1, int(entries["conc"].get().strip())),
min_pool_size=max(3, int(entries["minpool"].get().strip())),
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()),
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()),
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()),
firefox_path=firefox_path_var.get().strip(),
firefox_profile_dir=firefox_profile_var.get().strip(),
browser_clear_on_close=bool(br_clear_var.get()),
browser_disposable_profile=bool(br_disposable_var.get()),
browser_kill_on_chain_drop=bool(br_kill_drop_var.get()),
browser_auto_relaunch=bool(br_auto_relaunch_var.get()),
browser_lock_managed_profile=bool(br_lock_profile_var.get()),
browser_force_proxy=bool(br_force_proxy_var.get()),
browser_disable_webrtc=bool(br_disable_webrtc_var.get()),
browser_resist_fingerprinting=bool(br_rfp_var.get()),
browser_disable_telemetry=bool(br_telemetry_var.get()),
browser_first_party_isolation=bool(br_fpi_var.get()),
browser_strict_tracking_protection=bool(br_strict_tp_var.get()),
browser_timezone_utc=bool(br_tz_utc_var.get()),
)
if not (1 <= ns.local_port <= 65535):
raise ValueError("Port must be 1-65535")
svc.update_settings(ns)
save_settings(ns)
proxy_lbl.configure(text=f"{LISTEN_HOST}:{ns.local_port}")
if verbose:
_log("✓ Settings saved.")
except Exception as e:
_log(f"⚠ Settings error: {e!s}")
# ─────────────────────────────────────────────────────────────────────────
# STATUS REFRESHERS
# ─────────────────────────────────────────────────────────────────────────
def _refresh_sysproxy() -> None:
on = is_system_proxy_set()
sys_lbl.configure(text="SYS:ON" if on else "SYS:OFF",
text_color=GREEN if on else DIM)
def _refresh_fw(engaged: bool | None = None) -> None:
if engaged is None:
engaged = fw_is_engaged()
fw_lbl.configure(text="FW:ON" if engaged else "FW:OFF",
text_color=GREEN if engaged else DIM)
# ─────────────────────────────────────────────────────────────────────────
# MESSAGE PUMP
# ─────────────────────────────────────────────────────────────────────────
def _handle(m: dict[str, Any]) -> None:
t = m.get("type")
if t == "log":
_log(str(m.get("text", "")))
elif t == "state":
running = bool(m.get("running"))
status_dot.configure(text_color=GREEN if running else RED)
status_txt.configure(text="RUNNING" if running else "STOPPED")
_refresh_sysproxy()
if running:
tray.set_state("yellow", exit_ip=None)
if not running:
prog_bar.set(0)
phase_lbl.configure(text="idle")
countdown_lbl.configure(text="next: —", text_color=TEXT2)
tray.set_state("gray", exit_ip=None)
v_exit_ip.set("")
v_rotation.set("0")
elif t == "phase":
ph = str(m.get("phase", ""))
phase_labels = {
"validate": "validate",
"fetch": "fetch",
"running": "running",
"exit_check": "fixed exit",
"verify_chain": "verify chain",
"gost_start": "starting chain",
}
phase_lbl.configure(text=phase_labels.get(ph, ph or "idle"))
if ph == "validate":
prog_bar.configure(progress_color=YELLOW)
elif ph == "fetch":
prog_bar.configure(progress_color=ORANGE)
prog_bar.set(0.15)
elif ph == "exit_check":
prog_bar.configure(progress_color=YELLOW)
prog_bar.set(0.96)
elif ph == "gost_start":
prog_bar.configure(progress_color=GREEN)
prog_bar.set(0.985)
elif ph == "verify_chain":
prog_bar.configure(progress_color=GREEN)
prog_bar.set(0.98)
elif ph == "running":
prog_bar.configure(progress_color=GREEN)
prog_bar.set(1.0)
elif ph in ("idle", ""):
prog_bar.set(0)
elif t == "validate_progress":
d, tot = int(m.get("done", 0)), max(1, int(m.get("total", 1)))
prog_bar.set(d / tot)
elif t == "pool":
cnt = int(m.get("count", 0))
pool_lbl.configure(text=f"pool: {cnt}")
v_pool.set(str(cnt))
elif t == "real_ip":
ip = str(m.get("ip", ""))
your_ip_lbl.configure(text=f"Direct IP: {ip}")
v_real_ip.set(ip)
elif t == "vpn":
nonlocal outer_hop_label
if m.get("active"):
outer_hop_label = str(m.get("label", "VPN"))[:12]
vpn_status_lbl.configure(
text=f"VPN: {m.get('label', 'Active')}"
+ (f" ({m.get('adapter')})" if m.get("adapter") else ""),
text_color=GREEN,
)
leak_mode_lbl.configure(
text=f"Leak check: {m.get('leak_mode', 'VPN-aware')}",
text_color=TEXT2,
)
else:
outer_hop_label = "Direct"
vpn_status_lbl.configure(
text="VPN: not detected (direct connection)", text_color=YELLOW,
)
leak_mode_lbl.configure(
text=f"Leak check: {m.get('leak_mode', 'strict')}",
text_color=TEXT2,
)
elif t == "firewall":
_refresh_fw(bool(m.get("engaged")))
elif t == "rotation":
n = str(m.get("n", 0))
rotation_lbl.configure(text=f"rot: {n}")
v_rotation.set(n)
elif t == "countdown":
secs = int(m.get("secs", 0))
if secs > 0:
m_s, s_s = divmod(secs, 60)
countdown_lbl.configure(
text=f"next: {m_s}m{s_s:02d}s",
text_color=YELLOW if secs < 30 else TEXT2,
)
else:
countdown_lbl.configure(text="next: checking…", text_color=YELLOW)
elif t == "hops":
hops = [str(x) for x in (m.get("hops") or [])]
status = str(m.get("status", "connecting"))
exit_ip = m.get("exit_ip")
exit_s = str(exit_ip) if exit_ip else None
fix = normalize_proxy_url(svc.settings.manual_exit_proxy)
fixed_last = bool(fix) and not svc.settings.use_pinned_chain
_render_chain(hops, status, exit_ip, fixed_last=fixed_last)
_refresh_sysproxy()
tray_color = {"healthy": "green", "dead": "red", "connecting": "yellow"}.get(
status, "yellow"
)
tray.set_state(tray_color, exit_ip=exit_s)
v_exit_ip.set(exit_s if exit_s else "")
if status == "dead" and browser.is_running() and bool(br_kill_drop_var.get()):
browser.stop(dispose=bool(br_disposable_var.get()))
_refresh_browser_status()
_log("Browser stopped because chain dropped (Kill-on-drop enabled).")
def _pump() -> None:
try:
while True:
_handle(ui_q.get_nowait())
except queue.Empty:
pass
if browser_should_run[0] and not browser.is_running():
cfg = _browser_cfg()
now = time.monotonic()
if cfg.auto_relaunch and (now - browser_last_launch_ts[0]) >= 3.0:
if _launch_browser_with_cfg(cfg):
browser_last_launch_ts[0] = now
root.after(80, _pump)
# ─────────────────────────────────────────────────────────────────────────
# CLOSE / MINIMIZE
# ─────────────────────────────────────────────────────────────────────────
def _close() -> None:
_cancel_pulse()
browser_should_run[0] = False
browser.stop(dispose=bool(br_disposable_var.get()))
tray.stop()
svc.stop()
clear_system_proxy()
if is_admin() and fw_is_engaged():
fw_disengage()
root.destroy()
root.protocol("WM_DELETE_WINDOW", lambda: root.withdraw())
# ─────────────────────────────────────────────────────────────────────────
# INIT
# ─────────────────────────────────────────────────────────────────────────
_refresh_boot()
_refresh_sysproxy()
_refresh_fw()
_refresh_vpn_ui()
_refresh_browser_status()
_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(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("Privacy tab → MAC, hostname, IPv6, WebRTC, fingerprint audit, DNS checks.")
_log("Works with or without VPN — leak detection adapts automatically.")
_log("" * 60)
_pump()
root.mainloop()
if __name__ == "__main__":
main()