Add ban tester, signup prep, exit IP intel, and sticky-exit hold.

New GUI tabs probe popular sites for exit-IP bans and prep signup autofill through the chain; Live tab shows geo/ASN/datacenter flags, and sticky-exit keeps the same egress IP during signup flows.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Indiana Holmes
2026-05-19 15:37:28 -07:00
parent 8258ca0997
commit 26863fc2a5
8 changed files with 1587 additions and 3 deletions

View File

@@ -1,10 +1,11 @@
"""
Proxy God — GUI
Tabs: Live | Chain Builder | Browser | Privacy | Settings
Tabs: Live | Chain Builder | Browser | Ban Tester | Signup Prep | Privacy | Settings
"""
from __future__ import annotations
import logging
import ipaddress
import queue
import sys
import threading
@@ -13,6 +14,7 @@ from dataclasses import asdict
from datetime import datetime
from typing import Any, Literal
from tkinter import filedialog
from urllib.parse import urlparse
import customtkinter as ctk
@@ -36,6 +38,24 @@ from .firewall import (
request_admin_relaunch,
)
from .artifact_wipe import wipe_artifacts
from .ban_tester import BanTestResult, run_ban_tests, test_single_site
from .exit_intel import ExitIntel, fetch_exit_intel
from .signup_prep import (
SIGNUP_PRESET_KEYS,
SIGNUP_PRESETS,
AccountRecord,
SignupDraft,
append_account,
generate_password,
load_accounts,
load_draft,
random_first_name,
random_full_name,
random_last_name,
random_username,
resolve_signup_url,
save_draft,
)
from .browser_identity import (
COOKIE_LABELS,
COOKIE_MODES,
@@ -175,6 +195,7 @@ def main() -> None:
browser = BrowserSession()
browser_should_run = [False]
browser_last_launch_ts = [0.0]
service_running = [False]
# ── tray ─────────────────────────────────────────────────────────────────
tray = TrayIcon(
@@ -200,6 +221,17 @@ def main() -> None:
u = u.replace("http://", "").replace("socks5://", "s5://").replace("socks4://", "s4://").replace("https://", "")
return u[:28] + "" if len(u) > 30 else u
def _extract_ip_host(proxy_url: str) -> str | None:
p = urlparse(normalize_proxy_url(proxy_url))
host = (p.hostname or "").strip()
if not host:
return None
try:
ipaddress.ip_address(host)
return host
except ValueError:
return None
# ─────────────────────────────────────────────────────────────────────────
# HELPERS
# ─────────────────────────────────────────────────────────────────────────
@@ -422,9 +454,11 @@ def main() -> None:
tab_live = tabs.add(" Live ")
tab_chain = tabs.add(" Chain Builder ")
tab_browser = tabs.add(" Browser ")
tab_ban = tabs.add(" Ban Tester ")
tab_signup = tabs.add(" Signup Prep ")
tab_privacy = tabs.add(" Privacy ")
tab_settings = tabs.add(" Settings ")
for tab in (tab_live, tab_chain, tab_browser, tab_privacy, tab_settings):
for tab in (tab_live, tab_chain, tab_browser, tab_ban, tab_signup, tab_privacy, tab_settings):
tab.configure(fg_color=BG)
# ─── TAB: LIVE ───────────────────────────────────────────────────────────
@@ -450,6 +484,99 @@ def main() -> None:
_stat_card(stats_row, "Valid pool size", v_pool)
_stat_card(stats_row, "Rotations this session", v_rotation)
# ── Exit IP intel card (geo / ASN / datacenter) ──────────────────────────
intel_card = ctk.CTkFrame(tab_live, fg_color=CARD, corner_radius=8)
intel_card.pack(fill="x", pady=(0, 6))
intel_head = ctk.CTkFrame(intel_card, fg_color="transparent")
intel_head.pack(fill="x", padx=10, pady=(8, 2))
ctk.CTkLabel(
intel_head, text="Exit IP intel", font=(FONT, 11, "bold"), text_color=CYAN,
).pack(side="left")
v_intel_loc = ctk.StringVar(value="Location: —")
v_intel_asn = ctk.StringVar(value="ASN / ISP: —")
v_intel_tags = ctk.StringVar(value="Flags: —")
intel_body = ctk.CTkFrame(intel_card, fg_color="transparent")
intel_body.pack(fill="x", padx=10, pady=(0, 8))
ctk.CTkLabel(intel_body, textvariable=v_intel_loc, font=(FONT, 11), text_color=TEXT,
anchor="w").pack(anchor="w")
ctk.CTkLabel(intel_body, textvariable=v_intel_asn, font=(FONT, 11), text_color=TEXT2,
anchor="w").pack(anchor="w")
intel_tags_lbl = ctk.CTkLabel(intel_body, textvariable=v_intel_tags, font=(FONT, 11, "bold"),
text_color=GREEN, anchor="w")
intel_tags_lbl.pack(anchor="w")
intel_running = [False]
last_intel: dict[str, ExitIntel | None] = {"intel": None}
def _apply_intel(intel: ExitIntel | None) -> None:
last_intel["intel"] = intel
if intel is None or not intel.ok:
v_intel_loc.set("Location: —")
v_intel_asn.set("ASN / ISP: —")
v_intel_tags.set("Flags: " + (intel.detail if intel and intel.detail else ""))
intel_tags_lbl.configure(text_color=TEXT2)
return
loc_parts = [p for p in (intel.city, intel.region, intel.country) if p]
v_intel_loc.set("Location: " + (", ".join(loc_parts) if loc_parts else ""))
asn_str = f"AS{intel.asn} " if intel.asn else ""
org_str = intel.org or intel.isp or ""
v_intel_asn.set(f"ASN / ISP: {asn_str}{org_str}")
tags: list[str] = []
color = GREEN
if intel.is_datacenter:
tags.append("DATACENTER")
color = ORANGE
if intel.is_proxy_flagged:
tags.append("PROXY-FLAGGED")
color = RED
if intel.is_mobile:
tags.append("MOBILE")
if not tags:
tags.append("residential-like")
color = GREEN
v_intel_tags.set("Flags: " + " · ".join(tags))
intel_tags_lbl.configure(text_color=color)
def _refresh_exit_intel(silent: bool = False) -> None:
if intel_running[0]:
return
if not service_running[0] or not svc.current_chain:
if not silent:
_log("Exit intel: chain not running.")
_apply_intel(None)
return
intel_running[0] = True
v_intel_tags.set("Flags: looking up…")
intel_tags_lbl.configure(text_color=YELLOW)
proxy_url = f"http://{svc.settings.listen_addr()}"
def work() -> None:
intel = fetch_exit_intel(
proxy_url,
timeout_seconds=min(15.0, svc.settings.validation_timeout_seconds + 3.0),
)
def done() -> None:
_apply_intel(intel)
intel_running[0] = False
if not silent:
if intel.ok:
_log(f"Exit intel: {intel.summary()} (via {intel.source})")
else:
_log(f"Exit intel: lookup failed — {intel.detail}")
root.after(0, done)
threading.Thread(target=work, daemon=True).start()
intel_btn_row = ctk.CTkFrame(intel_card, fg_color="transparent")
intel_btn_row.pack(fill="x", padx=10, pady=(0, 8))
_btn(intel_btn_row, "Refresh intel", lambda: _refresh_exit_intel(False), w=110, h=24,
fg_color=ACCENT2, hover_color=ACCENT).pack(side="left")
# Log box
log_frame = ctk.CTkFrame(tab_live, fg_color=CARD, corner_radius=8)
log_frame.pack(fill="both", expand=True)
@@ -1178,6 +1305,9 @@ def main() -> None:
ok, msg = browser.stop(dispose=cfg.disposable_profile)
_log(msg)
_refresh_browser_status()
if svc.sticky_remaining() > 0:
svc.set_sticky(0)
_log("Sticky exit released (browser stopped).")
launch_browser_btn = _btn(
browser_action_row, "Launch Hardened Firefox", _launch_browser, w=190, h=34, font=(FONT, 12)
@@ -1189,6 +1319,568 @@ def main() -> None:
)
stop_browser_btn.pack(side="left")
# ─── TAB: BAN TESTER ───────────────────────────────────────────────────────
ban_scroll = ctk.CTkScrollableFrame(tab_ban, fg_color=BG, scrollbar_button_color=ACCENT)
ban_scroll.pack(fill="both", expand=True, padx=4, pady=4)
ban_head = ctk.CTkFrame(ban_scroll, fg_color=CARD, corner_radius=10, border_width=1, border_color=GLOW)
ban_head.pack(fill="x", padx=8, pady=(6, 8))
ctk.CTkLabel(
ban_head,
text="Ban List Tester",
font=(FONT, 13, "bold"),
text_color=CYAN,
).pack(anchor="w", padx=12, pady=(10, 2))
ctk.CTkLabel(
ban_head,
text="Tests high-traffic sites through your ACTIVE chain to detect likely exit-IP bans.",
font=(FONT, 10),
text_color=TEXT2,
).pack(anchor="w", padx=12, pady=(0, 8))
ban_status_var = ctk.StringVar(value="Status: idle")
ctk.CTkLabel(ban_head, textvariable=ban_status_var, font=(FONT, 11, "bold"), text_color=TEXT).pack(
anchor="w", padx=12, pady=(0, 8)
)
ban_results_frame = ctk.CTkFrame(
ban_scroll,
fg_color=CARD,
corner_radius=10,
border_width=1,
border_color=GLOW,
)
ban_results_frame.pack(fill="both", expand=True, padx=8, pady=(0, 8))
ban_rows_wrap = ctk.CTkFrame(ban_results_frame, fg_color="transparent")
ban_rows_wrap.pack(fill="both", expand=True, padx=8, pady=8)
def _ban_row(site: str, status: str, detail: str, color: str) -> None:
row = ctk.CTkFrame(ban_rows_wrap, fg_color=PANEL, corner_radius=6, height=30)
row.pack(fill="x", pady=2)
row.pack_propagate(False)
ctk.CTkLabel(row, text=site, font=(FONT, 11, "bold"), text_color=TEXT, width=140, anchor="w").pack(
side="left", padx=(8, 4)
)
ctk.CTkLabel(row, text=status, font=(FONT, 10, "bold"), text_color=color, width=80).pack(
side="left", padx=4
)
ctk.CTkLabel(row, text=detail, font=("Consolas", 10), text_color=TEXT2, anchor="w").pack(
side="left", fill="x", expand=True, padx=(6, 8)
)
def _render_ban_results(results: list[BanTestResult]) -> None:
for w in list(ban_rows_wrap.winfo_children()):
w.destroy()
if not results:
ctk.CTkLabel(
ban_rows_wrap,
text="No results yet.",
font=(FONT, 11),
text_color=TEXT2,
).pack(anchor="w", padx=8, pady=8)
return
for r in results:
if r.status == "ok":
st, color = "OK", GREEN
elif r.status == "banned":
st, color = "BANNED", ORANGE
else:
st, color = "ERROR", RED
_ban_row(r.site, st, r.detail, color)
_render_ban_results([])
ban_action = ctk.CTkFrame(ban_head, fg_color="transparent")
ban_action.pack(fill="x", padx=12, pady=(0, 10))
def _start_ban_test() -> None:
if not service_running[0] or not svc.current_chain:
ban_status_var.set("Status: chain not running")
_log("Ban tester: start the chain first, then run tests.")
return
proxy_url = f"http://{svc.settings.listen_addr()}"
ban_status_var.set("Status: running…")
ban_start_btn.configure(state="disabled")
_render_ban_results([])
def work() -> None:
timeout = min(18.0, max(8.0, float(svc.settings.validation_timeout_seconds) + 3.0))
results = run_ban_tests(proxy_url, timeout_seconds=timeout)
ok_n = sum(1 for r in results if r.status == "ok")
banned_n = sum(1 for r in results if r.status == "banned")
err_n = len(results) - ok_n - banned_n
def done() -> None:
_render_ban_results(results)
ban_status_var.set(f"Status: done | ok={ok_n} banned={banned_n} errors={err_n}")
_log(
f"Ban tester done via {proxy_url} — ok={ok_n}, banned={banned_n}, errors={err_n}."
)
ban_start_btn.configure(state="normal")
root.after(0, done)
threading.Thread(target=work, daemon=True).start()
ban_start_btn = _btn(
ban_action,
"Start ban test",
_start_ban_test,
w=120,
h=30,
fg_color=ACCENT2,
hover_color=ACCENT,
)
ban_start_btn.pack(side="left")
# ─── TAB: SIGNUP PREP ──────────────────────────────────────────────────────
signup_scroll = ctk.CTkScrollableFrame(tab_signup, fg_color=BG, scrollbar_button_color=ACCENT)
signup_scroll.pack(fill="both", expand=True, padx=4, pady=4)
signup_draft = load_draft()
def _signup_card(title: str, subtitle: str = "") -> ctk.CTkFrame:
ctk.CTkLabel(signup_scroll, text=title, font=(FONT, 13, "bold"), text_color=CYAN).pack(
anchor="w", padx=8, pady=(12, 0)
)
if subtitle:
ctk.CTkLabel(
signup_scroll, text=subtitle, font=(FONT, 10), text_color=TEXT2,
wraplength=900, justify="left",
).pack(anchor="w", padx=8, pady=(0, 4))
f = ctk.CTkFrame(signup_scroll, fg_color=CARD, corner_radius=10, border_width=1, border_color=GLOW)
f.pack(fill="x", padx=8, pady=(4, 8))
return f
signup_head = _signup_card(
"Signup autofill (manual submit)",
"Opens signup pages through your chain and autofills your fields. You solve captchas and click submit.",
)
site_row = ctk.CTkFrame(signup_head, fg_color="transparent")
site_row.pack(fill="x", padx=12, pady=(10, 4))
ctk.CTkLabel(site_row, text="Site", width=70, anchor="w", font=(FONT, 10), text_color=TEXT2).pack(side="left")
signup_site_keys = list(SIGNUP_PRESET_KEYS) + ["custom"]
signup_site_labels = [
(SIGNUP_PRESETS[k].label if k in SIGNUP_PRESETS else "Custom URL")
for k in signup_site_keys
]
signup_site_var = ctk.StringVar(
value=next(
(signup_site_labels[i] for i, k in enumerate(signup_site_keys) if k == signup_draft.site_key),
signup_site_labels[0],
)
)
signup_site_menu = ctk.CTkOptionMenu(
site_row, values=signup_site_labels, variable=signup_site_var,
fg_color=BG, button_color=ACCENT2, button_hover_color=ACCENT, text_color=TEXT, width=260,
)
signup_site_menu.pack(side="left", padx=(0, 8))
def _signup_site_key() -> str:
label = signup_site_var.get()
for i, lbl in enumerate(signup_site_labels):
if lbl == label:
return signup_site_keys[i]
return signup_site_keys[0]
custom_url_row = ctk.CTkFrame(signup_head, fg_color="transparent")
custom_url_row.pack(fill="x", padx=12, pady=(0, 4))
ctk.CTkLabel(custom_url_row, text="Custom URL", width=70, anchor="w", font=(FONT, 10), text_color=TEXT2).pack(side="left")
signup_custom_url = ctk.CTkEntry(custom_url_row, height=28, fg_color=BG, border_color=ACCENT)
signup_custom_url.pack(side="left", fill="x", expand=True)
signup_custom_url.insert(0, signup_draft.custom_url or "")
def _signup_field(parent: Any, label: str, show: str = "") -> ctk.CTkEntry:
row = ctk.CTkFrame(parent, fg_color="transparent")
row.pack(fill="x", padx=12, pady=3)
ctk.CTkLabel(row, text=label, width=70, anchor="w", font=(FONT, 10), text_color=TEXT2).pack(side="left")
e = ctk.CTkEntry(row, height=28, fg_color=BG, border_color=ACCENT, show=show)
e.pack(side="left", fill="x", expand=True)
return e
signup_email = _signup_field(signup_head, "Email")
signup_email.insert(0, signup_draft.email or "")
signup_pass = _signup_field(signup_head, "Password", show="*")
signup_pass.insert(0, signup_draft.password or "")
signup_user = _signup_field(signup_head, "Username")
signup_user.insert(0, signup_draft.username or "")
signup_fname = _signup_field(signup_head, "First name")
signup_fname.insert(0, signup_draft.first_name or "")
signup_lname = _signup_field(signup_head, "Last name")
signup_lname.insert(0, signup_draft.last_name or "")
signup_notes = _signup_field(signup_head, "Notes")
signup_notes.insert(0, signup_draft.notes or "")
def _clipboard_copy(text: str, label: str) -> None:
t = (text or "").strip()
if not t:
_log(f"Signup prep: nothing to copy for {label}.")
return
root.clipboard_clear()
root.clipboard_append(t)
root.update()
_log(f"Copied {label} to clipboard.")
def _collect_signup_draft() -> SignupDraft:
return SignupDraft(
site_key=_signup_site_key(),
custom_url=signup_custom_url.get().strip(),
email=signup_email.get().strip(),
password=signup_pass.get().strip(),
username=signup_user.get().strip(),
first_name=signup_fname.get().strip(),
last_name=signup_lname.get().strip(),
notes=signup_notes.get().strip(),
)
def _persist_signup_draft() -> SignupDraft:
d = _collect_signup_draft()
save_draft(d)
return d
signup_btn_row = ctk.CTkFrame(signup_head, fg_color="transparent")
signup_btn_row.pack(fill="x", padx=12, pady=(8, 6))
def _gen_signup_password() -> None:
pw = generate_password()
signup_pass.delete(0, "end")
signup_pass.insert(0, pw)
_persist_signup_draft()
_log("Generated signup password.")
def _gen_signup_names() -> None:
f, l = random_full_name()
signup_fname.delete(0, "end"); signup_fname.insert(0, f)
signup_lname.delete(0, "end"); signup_lname.insert(0, l)
_persist_signup_draft()
_log(f"Generated name: {f} {l}")
def _gen_signup_username() -> None:
f = signup_fname.get().strip() or random_first_name()
l = signup_lname.get().strip() or random_last_name()
u = random_username(f, l)
signup_user.delete(0, "end"); signup_user.insert(0, u)
_persist_signup_draft()
_log(f"Generated username: {u}")
def _gen_signup_everything() -> None:
f, l = random_full_name()
signup_fname.delete(0, "end"); signup_fname.insert(0, f)
signup_lname.delete(0, "end"); signup_lname.insert(0, l)
signup_user.delete(0, "end"); signup_user.insert(0, random_username(f, l))
signup_pass.delete(0, "end"); signup_pass.insert(0, generate_password())
_persist_signup_draft()
_log(f"Generated identity: {f} {l}")
_btn(signup_btn_row, "Gen password", _gen_signup_password, w=100, h=28,
fg_color=DIM, hover_color=ACCENT).pack(side="left", padx=(0, 6))
_btn(signup_btn_row, "Gen name", _gen_signup_names, w=80, h=28,
fg_color=DIM, hover_color=ACCENT).pack(side="left", padx=(0, 6))
_btn(signup_btn_row, "Gen username", _gen_signup_username, w=100, h=28,
fg_color=DIM, hover_color=ACCENT).pack(side="left", padx=(0, 6))
_btn(signup_btn_row, "Gen ALL", _gen_signup_everything, w=80, h=28,
fg_color=ACCENT2, hover_color=ACCENT).pack(side="left", padx=(0, 12))
_btn(signup_btn_row, "Copy email", lambda: _clipboard_copy(signup_email.get(), "email"),
w=88, h=28, fg_color=DIM, hover_color=ACCENT).pack(side="left", padx=(0, 6))
_btn(signup_btn_row, "Copy pass", lambda: _clipboard_copy(signup_pass.get(), "password"),
w=88, h=28, fg_color=DIM, hover_color=ACCENT).pack(side="left", padx=(0, 6))
# Sticky exit + paranoid mode toggles
signup_opts_row = ctk.CTkFrame(signup_head, fg_color="transparent")
signup_opts_row.pack(fill="x", padx=12, pady=(0, 6))
sticky_exit_var = ctk.BooleanVar(value=True)
paranoid_signup_var = ctk.BooleanVar(value=False)
ctk.CTkCheckBox(
signup_opts_row, text="Sticky exit (hold final hop while signup is open)",
variable=sticky_exit_var, font=(FONT, 10),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT,
).pack(side="left", padx=(0, 12))
ctk.CTkCheckBox(
signup_opts_row, text="Paranoid mode (disposable profile + DNS flush)",
variable=paranoid_signup_var, font=(FONT, 10),
fg_color=ACCENT2, hover_color=ACCENT, text_color=TEXT,
).pack(side="left")
# ── Pre-flight checklist ─────────────────────────────────────────────────
preflight_card = _signup_card(
"Pre-flight check",
"Verifies chain, target site reachability, and exit-IP intel before you sign up.",
)
pf_rows: dict[str, dict[str, ctk.StringVar | ctk.CTkLabel]] = {}
def _pf_row(key: str, label: str) -> None:
row = ctk.CTkFrame(preflight_card, fg_color=PANEL, corner_radius=6)
row.pack(fill="x", padx=12, pady=2)
dot = ctk.CTkLabel(row, text="", font=(FONT, 14), text_color=DIM, width=18)
dot.pack(side="left", padx=(8, 4))
ctk.CTkLabel(row, text=label, font=(FONT, 11, "bold"), text_color=TEXT,
width=160, anchor="w").pack(side="left")
var = ctk.StringVar(value="not run")
ctk.CTkLabel(row, textvariable=var, font=(FONT, 10), text_color=TEXT2,
anchor="w").pack(side="left", fill="x", expand=True, padx=(6, 8), pady=4)
pf_rows[key] = {"var": var, "dot": dot}
_pf_row("chain", "Chain running")
_pf_row("exit_ip", "Exit IP")
_pf_row("intel", "IP intel (geo/ASN)")
_pf_row("target", "Target site reachable")
def _pf_set(key: str, status: str, detail: str) -> None:
r = pf_rows.get(key)
if not r:
return
color = {"ok": GREEN, "warn": ORANGE, "fail": RED, "running": YELLOW}.get(status, DIM)
r["dot"].configure(text_color=color)
r["var"].set(detail)
pf_summary_var = ctk.StringVar(value="Not run.")
ctk.CTkLabel(preflight_card, textvariable=pf_summary_var, font=(FONT, 10, "bold"),
text_color=TEXT).pack(anchor="w", padx=12, pady=(6, 8))
pf_running = [False]
def _run_preflight() -> None:
if pf_running[0]:
return
draft = _persist_signup_draft()
url = resolve_signup_url(draft)
if not url:
pf_summary_var.set("Pick a site or enter a custom URL first.")
return
pf_running[0] = True
for k in ("chain", "exit_ip", "intel", "target"):
_pf_set(k, "running", "checking…")
pf_summary_var.set("Running pre-flight…")
chain_ok = service_running[0] and bool(svc.current_chain)
if not chain_ok:
_pf_set("chain", "fail", "chain not running")
_pf_set("exit_ip", "fail", "n/a")
_pf_set("intel", "fail", "n/a")
_pf_set("target", "fail", "n/a")
pf_summary_var.set("Start the chain, then re-run pre-flight.")
pf_running[0] = False
return
_pf_set("chain", "ok", f"{len(svc.current_chain)} hop(s) up")
proxy_url = f"http://{svc.settings.listen_addr()}"
target_host = urlparse(url).hostname or "(unknown)"
def work() -> None:
timeout = min(15.0, max(8.0, float(svc.settings.validation_timeout_seconds) + 2.0))
intel = fetch_exit_intel(proxy_url, timeout_seconds=timeout)
site_res = test_single_site(proxy_url, target_host, url, timeout_seconds=timeout)
def done() -> None:
ip = (v_exit_ip.get() or "").strip()
if ip and ip != "":
_pf_set("exit_ip", "ok", ip)
else:
_pf_set("exit_ip", "warn", "no IP reported yet")
if intel.ok:
_apply_intel(intel)
if intel.is_proxy_flagged:
_pf_set("intel", "fail", f"proxy-flagged · {intel.org or intel.isp}")
elif intel.is_datacenter:
_pf_set("intel", "warn", f"datacenter ASN · {intel.org or intel.isp}")
else:
loc = ", ".join([b for b in (intel.city, intel.country) if b]) or ""
_pf_set("intel", "ok", f"{loc} · AS{intel.asn} {intel.org or intel.isp}")
else:
_pf_set("intel", "warn", intel.detail or "lookup failed")
if site_res.status == "ok":
_pf_set("target", "ok", f"{target_host} {site_res.detail}")
elif site_res.status == "banned":
_pf_set("target", "fail", f"likely banned · {site_res.detail}")
else:
_pf_set("target", "warn", site_res.detail)
warns: list[str] = []
if intel.ok and intel.is_proxy_flagged:
warns.append("exit IP is proxy-flagged")
if intel.ok and intel.is_datacenter:
warns.append("exit IP is a datacenter ASN")
if site_res.status == "banned":
warns.append("target site likely blocks this IP")
if not warns:
pf_summary_var.set("All checks passed — good to sign up.")
else:
pf_summary_var.set("Warnings: " + "; ".join(warns))
pf_running[0] = False
root.after(0, done)
threading.Thread(target=work, daemon=True).start()
pf_btn_row = ctk.CTkFrame(preflight_card, fg_color="transparent")
pf_btn_row.pack(fill="x", padx=12, pady=(0, 10))
_btn(pf_btn_row, "Run pre-flight", _run_preflight, w=130, h=28,
fg_color=ACCENT2, hover_color=ACCENT).pack(side="left")
signup_status_var = ctk.StringVar(value="Ready — start chain, then Open & Autofill.")
ctk.CTkLabel(signup_head, textvariable=signup_status_var, font=(FONT, 10), text_color=TEXT2).pack(
anchor="w", padx=12, pady=(0, 8)
)
signup_action = ctk.CTkFrame(signup_scroll, fg_color="transparent")
signup_action.pack(fill="x", padx=8, pady=(0, 6))
signup_launch_retries = [0]
accounts_card = _signup_card("Saved accounts", "Logged locally with exit IP + hop used at save time.")
accounts_list = ctk.CTkScrollableFrame(
accounts_card, fg_color=BG, height=180, corner_radius=6, scrollbar_button_color=ACCENT,
)
accounts_list.pack(fill="x", padx=12, pady=(8, 10))
def _refresh_accounts_ui() -> None:
for w in list(accounts_list.winfo_children()):
w.destroy()
rows = load_accounts()
if not rows:
ctk.CTkLabel(accounts_list, text="No saved accounts yet.", font=(FONT, 10), text_color=TEXT2).pack(
anchor="w", padx=8, pady=8
)
return
for rec in rows[:40]:
line = f"{rec.created_at[:16]} {rec.site} {rec.email} exit={rec.exit_ip or ''}"
ctk.CTkLabel(
accounts_list, text=line, font=("Consolas", 10), text_color=TEXT, anchor="w",
).pack(anchor="w", padx=8, pady=2)
def _current_exit_meta() -> tuple[str, str]:
ip = (v_exit_ip.get() or "").strip()
if ip == "":
ip = ""
hop = ""
chain = svc.current_chain
if chain:
hop = redact_proxy_url(chain[-1])
return ip, hop
def _launch_signup_autofill() -> None:
draft = _persist_signup_draft()
url = resolve_signup_url(draft)
if not url:
signup_status_var.set("Missing signup URL.")
_log("Signup prep: pick a site or enter a custom URL.")
return
if not draft.email and not draft.username:
signup_status_var.set("Enter email or username.")
_log("Signup prep: email or username required for autofill.")
return
if not draft.password:
signup_status_var.set("Enter or generate a password.")
_log("Signup prep: password required for autofill.")
return
cfg = _browser_cfg()
cfg.lock_managed_profile = True
paranoid = bool(paranoid_signup_var.get())
if paranoid:
cfg.disposable_profile = True
cfg.clear_on_close = True
cfg.kill_on_chain_drop = True
ok_dns, msg_dns = flush_dns_cache()
_log(f"Paranoid mode: DNS flush — {msg_dns}" if ok_dns else f"Paranoid mode: DNS flush failed — {msg_dns}")
if not service_running[0] or not svc.current_chain:
if signup_launch_retries[0] == 0:
_log("Signup prep: starting chain first…")
_start()
signup_launch_retries[0] += 1
if signup_launch_retries[0] > 12:
signup_status_var.set("Chain did not start in time.")
signup_launch_retries[0] = 0
_log("Signup prep: chain not ready — start chain manually.")
return
signup_status_var.set("Waiting for chain…")
root.after(2500, _launch_signup_autofill)
return
signup_launch_retries[0] = 0
if bool(sticky_exit_var.get()):
svc.set_sticky(1800)
_log("Sticky exit engaged — auto-rotation suspended for 30 min.")
ok, msg = browser.launch(
cfg, LISTEN_HOST, svc.settings.local_port,
start_url=url, signup_draft=draft,
)
_log(msg)
_refresh_browser_status()
if ok:
preset = SIGNUP_PRESETS.get(draft.site_key)
label = preset.label if preset else "Custom"
extras = []
if bool(sticky_exit_var.get()):
extras.append("sticky")
if paranoid:
extras.append("paranoid")
tag = f" [{' · '.join(extras)}]" if extras else ""
signup_status_var.set(f"Opened {label}{tag} — autofill active (you submit + captcha).")
_log(f"Signup prep: opened {url} with autofill extension{tag}.")
else:
signup_status_var.set("Browser launch failed.")
svc.set_sticky(0)
def _save_signup_account() -> None:
draft = _persist_signup_draft()
url = resolve_signup_url(draft)
exit_ip, exit_hop = _current_exit_meta()
preset = SIGNUP_PRESETS.get(draft.site_key)
site_label = preset.label if preset else "Custom"
rec = AccountRecord(
id=datetime.now().strftime("%Y%m%d%H%M%S%f"),
site=site_label,
url=url,
email=draft.email,
password=draft.password,
username=draft.username,
first_name=draft.first_name,
last_name=draft.last_name,
exit_ip=exit_ip,
exit_hop=exit_hop,
notes=draft.notes,
status="created",
created_at=datetime.now().isoformat(timespec="seconds"),
)
append_account(rec)
_refresh_accounts_ui()
signup_status_var.set(f"Saved account for {site_label}.")
_log(f"Signup prep: saved account {draft.email or draft.username} ({site_label}).")
def _preflight_then_open() -> None:
_run_preflight()
def follow_up(attempts: int = 0) -> None:
if pf_running[0] and attempts < 30:
root.after(500, lambda: follow_up(attempts + 1))
return
_launch_signup_autofill()
root.after(500, follow_up)
signup_open_btn = _btn(
signup_action, "Open & Autofill", _launch_signup_autofill,
w=140, h=34, fg_color=ACCENT2, hover_color=ACCENT, font=(FONT, 12),
)
signup_open_btn.pack(side="left", padx=(0, 8))
_btn(
signup_action, "Pre-flight + Open", _preflight_then_open,
w=150, h=34, fg_color=ACCENT, hover_color=ACCENT2,
).pack(side="left", padx=(0, 8))
_btn(
signup_action, "Save account", _save_signup_account,
w=110, h=34, fg_color=GREEN, hover_color="#00b377",
).pack(side="left", padx=(0, 8))
_btn(
signup_action, "Refresh list", _refresh_accounts_ui,
w=100, h=34, fg_color=DIM, hover_color=ACCENT,
).pack(side="left")
_refresh_accounts_ui()
# ─── TAB: PRIVACY ──────────────────────────────────────────────────────────
priv_scroll = ctk.CTkScrollableFrame(tab_privacy, fg_color=BG,
scrollbar_button_color=ACCENT)
@@ -1635,6 +2327,7 @@ def main() -> None:
elif t == "state":
running = bool(m.get("running"))
service_running[0] = running
status_dot.configure(text_color=GREEN if running else RED)
status_txt.configure(text="RUNNING" if running else "STOPPED")
_refresh_sysproxy()
@@ -1740,15 +2433,24 @@ def main() -> None:
status = str(m.get("status", "connecting"))
exit_ip = m.get("exit_ip")
exit_s = str(exit_ip) if exit_ip else None
# If the live check hasn't resolved yet, show the explicit final-hop IP
# (when available) so tunnel/fixed-exit setups still expose the egress node.
if not exit_s and hops:
exit_s = _extract_ip_host(hops[-1])
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)
_render_chain(hops, status, exit_s, 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)
prev_exit = v_exit_ip.get()
v_exit_ip.set(exit_s if exit_s else "")
if status == "healthy" and exit_s and exit_s != prev_exit:
root.after(400, lambda: _refresh_exit_intel(silent=True))
elif status != "healthy":
_apply_intel(None)
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()
@@ -1800,6 +2502,7 @@ def main() -> None:
_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("Works with or without VPN — leak detection adapts automatically.")
_log("" * 60)