fix: audit pass — WebRTC audit, HTTPS gate, leak compare, kill-switch guard, doc backlog
Some checks failed
CI / Test Python 3.10 (push) Has been cancelled
CI / Test Python 3.11 (push) Has been cancelled
CI / Test Python 3.12 (push) Has been cancelled

This commit is contained in:
Dr Jones
2026-05-22 18:00:45 -07:00
parent 255fdf3e8c
commit 04d486a335
9 changed files with 164 additions and 31 deletions

View File

@@ -1773,7 +1773,7 @@ def _main_inner() -> None:
preflight_card = _signup_card(
"Pre-flight check",
"Exhaustive readiness check: chain, exit IP, geo/ASN, DNS leak, WebRTC, "
"IPv6, fingerprint, target site reachability, and direct-IP comparison.",
"IPv6, HTTPS tunnel, target site reachability, and direct-IP comparison.",
)
pf_rows: dict[str, dict[str, ctk.StringVar | ctk.CTkLabel]] = {}
@@ -1853,8 +1853,11 @@ def _main_inner() -> None:
target_host = urlparse(url).hostname or "(unknown)"
def work() -> None:
from .leak_detect import is_chain_leak, leak_reason
timeout = min(15.0, max(8.0, float(svc.settings.validation_timeout_seconds) + 2.0))
warns: list[str] = []
vpn_active = detect_vpn().active
# ── 1. Direct IP (bypass chain) ──────────────────────────────────
from .validator import get_direct_ip as _get_direct_ip
@@ -1882,12 +1885,12 @@ def _main_inner() -> None:
_pf_set("exit_ip", "ok", ip_)
else:
_pf_set("exit_ip", "warn", "no exit IP yet")
# IP leak compare
# IP leak compare (same logic as production leak_detect)
if direct_ip and ip_ and ip_ not in ("", ""):
if direct_ip == ip_:
_pf_set("leak_compare", "fail",
f"exit == direct ({ip_}) — chain not working!")
warns.append("exit IP matches direct IP (chain not routing)")
if is_chain_leak(ip_, direct_ip, vpn_active):
reason = leak_reason(ip_, direct_ip, vpn_active)
_pf_set("leak_compare", "fail", reason[:80])
warns.append(f"IP leak: {reason}")
else:
_pf_set("leak_compare", "ok",
f"exit {ip_} ≠ direct {direct_ip}")
@@ -2148,12 +2151,38 @@ def _main_inner() -> None:
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}.")
# Auto-save entry so the account is recorded even if user forgets to save manually
_save_signup_account()
# Record draft metadata only — password saved when user clicks Save Account
_save_signup_account_draft_only()
else:
signup_status_var.set("Browser launch failed.")
svc.set_sticky(0)
def _save_signup_account_draft_only() -> None:
"""After browser open: save email/exit metadata without password until user confirms."""
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="",
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="pending",
created_at=datetime.now().isoformat(timespec="seconds"),
)
append_account(rec)
_refresh_accounts_ui()
_log(f"Signup prep: draft account {draft.email or draft.username} ({site_label}) — save password after signup.")
def _save_signup_account() -> None:
draft = _persist_signup_draft()
url = resolve_signup_url(draft)

View File

@@ -75,17 +75,18 @@ _BANNED_TEXT_HINTS = (
"temporarily blocked",
"request blocked",
"unusual traffic",
"captcha",
"challenge required",
"bot detected",
"suspicious activity",
"geo-blocked",
"not available in your region",
"your ip",
"ip address",
"cloudflare ray",
"ddos protection",
"attention required",
"complete the captcha",
"captcha required",
"g-recaptcha",
"hcaptcha",
)

View File

@@ -267,7 +267,7 @@ def _is_safe_https_url(url: str) -> bool:
"""Return True for HTTPS URLs pointing at public hosts (not RFC-1918/link-local)."""
try:
p = urlparse(url)
if p.scheme not in ("http", "https"):
if p.scheme != "https":
return False
host = p.hostname or ""
if not host:

View File

@@ -168,7 +168,12 @@ def emergency_disengage() -> None:
Unlike ``disengage()``, this never raises and does not require prior
admin check — it simply tries, logs the outcome, and returns. Safe to
call from atexit or signal handlers where exceptions must not propagate.
No-op when our rules are not present (avoids touching firewall policy on
every process exit).
"""
if not is_engaged():
return
try:
_set_outbound_policy("blockinbound,allowoutbound")
_delete_rules()

View File

@@ -141,14 +141,29 @@ def _check_webrtc_policy() -> tuple[bool, str]:
r"SOFTWARE\Policies\Google\Chrome",
r"SOFTWARE\Policies\Microsoft\Edge",
)
_DWORD_DISABLE = 3 # disable_non_proxied_udp
_STR_KEY = "WebRtcIPHandling"
_STR_DISABLE = "disable_non_proxied_udp"
found = []
for p in paths:
try:
with winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE, p, 0, winreg.KEY_QUERY_VALUE
) as key:
v = int(winreg.QueryValueEx(key, "DefaultWebRtcIpHandlingPolicy")[0])
if v == 2:
ok = False
try:
v = int(winreg.QueryValueEx(key, "DefaultWebRtcIpHandlingPolicy")[0])
if v == _DWORD_DISABLE:
ok = True
except OSError:
pass
try:
v_str = str(winreg.QueryValueEx(key, _STR_KEY)[0]).strip().lower()
if v_str == _STR_DISABLE:
ok = True
except OSError:
pass
if ok:
found.append(p.split("\\")[-1])
except OSError:
continue

View File

@@ -390,6 +390,14 @@ class ChainService:
)
# Pinned (manual) chain mode — skip pool management
if self._settings.use_pinned_chain and not self._settings.pinned_chain:
self._notify({
"type": "log",
"text": (
"Pinned chain enabled but list is empty — add hops in Chain Builder "
"or disable 'Use pinned chain'."
),
})
if self._settings.use_pinned_chain and self._settings.pinned_chain:
chain = list(self._settings.pinned_chain)
rotation_num += 1
@@ -564,22 +572,27 @@ class ChainService:
self._proc = None
return False
# HTTPS CONNECT must work before the chain is marked healthy.
https_ok, https_msg = await check_https_tunnel(local_proxy, timeout)
if not https_ok:
self._notify({"type": "hops", "hops": chain, "status": "dead", "exit_ip": exit_ip})
self._notify({"type": "log", "text": (
"HTTPS tunnel FAILED — chain forwards plain HTTP but refuses CONNECT. "
"Browsers will time out on HTTPS sites. Rotating. " + https_msg
)})
fixed = self._manual_exit_url()
for h in chain:
if fixed and h == fixed:
continue
self._blacklist.add(h)
self._available = [x for x in self._available if x != h]
terminate_process(self._proc)
self._proc = None
return False
self._notify({"type": "hops", "hops": chain, "status": "healthy", "exit_ip": exit_ip})
self._notify({"type": "log", "text": f"✓ Chain healthy — Exit IP: {exit_ip}"})
# HTTPS-tunnel probe: a chain can pass HTTP IP check but refuse CONNECT.
# Without this warning, "all proxies green" yet "every browser broken"
# is a black-box failure for the user.
https_ok, https_msg = await check_https_tunnel(local_proxy, timeout)
if https_ok:
self._notify({"type": "log", "text": f"✓ HTTPS tunnel OK — {https_msg}"})
else:
self._notify({"type": "log", "text": (
"⚠ HTTPS tunnel FAILED — chain forwards plain HTTP but refuses "
"CONNECT. Browsers will time out on every HTTPS page (i.e. "
"every site). Replace the proxies that don't support CONNECT, "
"or use a SOCKS5 / paid HTTPS-capable exit. " + https_msg
)})
self._notify({"type": "log", "text": f"✓ HTTPS tunnel OK — {https_msg}"})
self._notify({"type": "phase", "phase": "running"})
# Pre-flight: surface Group Policy locks (they will override us).