Files
proxy-god/proxy_chain_manager/signup_prep.py
Dr Jones 01c19349ee feat: exhaustive feature expansion — cookie modes, DNS/WebRTC testers, map, Firefox fix
Cookie system:
- Expand from 5 to 12 fully-specified CookiePolicy modes in browser_identity.py
- Add CookiePolicy dataclass: behavior, lifetime, TCP partitioning, clearOnShutdown.*
- browser_profile.py emits full cookie pref set from policy object
- UI dropdown widened to 680px with live description label per mode

Firefox launch fix:
- Detect Firefox via Windows registry, AppData, and shutil.which
- Use DETACHED_PROCESS|CREATE_NO_WINDOW|CREATE_NEW_PROCESS_GROUP flags
- Wait up to 3s for firefox.exe in tasklist instead of polling parent pid
- taskkill on stop() to terminate all firefox.exe processes

DNS leak tester:
- dns_leak.py: FullDnsLeakReport dataclass, run_dns_leak_test comparing
  proxy DoH resolution vs direct system DNS

WebRTC tester:
- webrtc_check.py: STUN UDP probe, registry policy check, user.js pref check

Ban tester:
- Added SITES_SHOPPING, SITES_CRYPTO, SITES_DNS categories
- Parallel execution via ThreadPoolExecutor
- Expanded banned-text hint keywords

Fingerprint audit:
- OS identity checks: hostname, MAC, GUID, OS version, timezone, screen res
- Browser consistency analysis of user.js

Neon world map:
- world_map.png bundled; chain_map.py renders hop arcs over it with glow effect

Signup prep:
- Auto-save account on Open & Autofill; Copy Email / Copy Pass buttons
- Auto-fill custom URL when preset site selected
- PyInstaller-safe path resolution for signup_extension

Spec:
- Bundle signup_extension dir and world_map.png as PyInstaller data files

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-21 16:47:22 -07:00

424 lines
13 KiB
Python

from __future__ import annotations
import json
import secrets
import shutil
import string
import uuid
from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from pathlib import Path
from typing import Any
from .paths import app_data_dir
_EXT_ID = "signup-autofill@proxygod"
def _ext_src_path() -> Path:
"""Locate the signup_extension dir whether running from source or frozen."""
import sys
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
return Path(sys._MEIPASS) / "proxy_chain_manager" / "signup_extension"
return Path(__file__).resolve().parent / "signup_extension"
@dataclass(frozen=True)
class SignupSitePreset:
key: str
label: str
url: str
hosts: tuple[str, ...]
extra_selectors: dict[str, list[str]] = field(default_factory=dict)
SIGNUP_PRESETS: dict[str, SignupSitePreset] = {
"google": SignupSitePreset(
key="google",
label="Google",
url="https://accounts.google.com/signup/v2/createaccount?flowName=GlifWebSignIn&flowEntry=SignUp",
hosts=("accounts.google.com", "google.com"),
extra_selectors={
"first_name": ['input[name="firstName"]'],
"last_name": ['input[name="lastName"]'],
"email": ['input[name="Username"]', 'input[type="email"]'],
"password": ['input[name="Passwd"]'],
"password_confirm": ['input[name="PasswdAgain"]'],
},
),
"protonmail": SignupSitePreset(
key="protonmail",
label="Proton Mail",
url="https://account.proton.me/signup",
hosts=("account.proton.me", "proton.me"),
extra_selectors={
"email": ['input[id="email"]', 'input[name="email"]', 'input[type="email"]'],
"password": ['input[id="password"]', 'input[name="password"]'],
"password_confirm": ['input[id="password-confirm"]', 'input[name="passwordConfirmation"]'],
},
),
"hydraproxy": SignupSitePreset(
key="hydraproxy",
label="HydraProxy",
url="https://dashboard.hydraproxy.com/register",
hosts=("dashboard.hydraproxy.com", "hydraproxy.com"),
extra_selectors={
"email": ['input[name="email"]', 'input[type="email"]', 'input[id="email"]'],
"password": ['input[name="password"]', 'input[type="password"]'],
"password_confirm": ['input[name="password_confirmation"]', 'input[name="confirmPassword"]'],
"username": ['input[name="username"]', 'input[name="name"]'],
},
),
"outlook": SignupSitePreset(
key="outlook",
label="Outlook / Microsoft",
url="https://signup.live.com/signup",
hosts=("signup.live.com", "login.live.com", "account.microsoft.com"),
extra_selectors={
"email": ['input[name="MemberName"]', 'input[type="email"]'],
"password": ['input[name="Password"]', 'input[name="PasswordInput"]'],
"first_name": ['input[name="FirstName"]'],
"last_name": ['input[name="LastName"]'],
},
),
"github": SignupSitePreset(
key="github",
label="GitHub",
url="https://github.com/signup",
hosts=("github.com",),
extra_selectors={
"email": ['input[name="user[email]"]', 'input[autocomplete="email"]'],
"password": ['input[name="user[password]"]', 'input[autocomplete="new-password"]'],
"username": ['input[name="user[login]"]'],
},
),
"reddit": SignupSitePreset(
key="reddit",
label="Reddit",
url="https://www.reddit.com/register",
hosts=("www.reddit.com", "reddit.com"),
extra_selectors={
"email": ['input[name="email"]', 'input[type="email"]'],
"username": ['input[name="username"]'],
"password": ['input[name="password"]'],
},
),
"discord": SignupSitePreset(
key="discord",
label="Discord",
url="https://discord.com/register",
hosts=("discord.com",),
extra_selectors={
"email": ['input[name="email"]', 'input[type="email"]'],
"username": ['input[name="username"]'],
"password": ['input[name="password"]'],
},
),
"x_twitter": SignupSitePreset(
key="x_twitter",
label="X / Twitter",
url="https://x.com/i/flow/signup",
hosts=("x.com", "twitter.com"),
extra_selectors={
"email": ['input[name="email"]', 'input[type="email"]'],
"username": ['input[name="username"]'],
"password": ['input[name="password"]'],
"first_name": ['input[name="name"]'],
},
),
"telegram": SignupSitePreset(
key="telegram",
label="Telegram Web",
url="https://web.telegram.org/",
hosts=("web.telegram.org", "telegram.org"),
),
"tutanota": SignupSitePreset(
key="tutanota",
label="Tuta Mail",
url="https://app.tuta.com/signup",
hosts=("app.tuta.com", "tutanota.com", "tuta.com"),
extra_selectors={
"email": ['input[id="mailAddress"]', 'input[name="mailAddress"]'],
"password": ['input[id="password1"]', 'input[type="password"]'],
"password_confirm": ['input[id="password2"]'],
},
),
"mailcom": SignupSitePreset(
key="mailcom",
label="mail.com",
url="https://signup.mail.com/",
hosts=("signup.mail.com", "mail.com"),
extra_selectors={
"username": ['input[name="email_user"]', 'input[name="username"]'],
"password": ['input[name="password"]'],
"first_name": ['input[name="firstName"]'],
"last_name": ['input[name="lastName"]'],
},
),
}
# Public site-key list in display order. Useful for building dropdowns.
SIGNUP_PRESET_KEYS: tuple[str, ...] = (
"google", "protonmail", "outlook", "tutanota", "mailcom",
"github", "reddit", "discord", "x_twitter", "telegram",
"hydraproxy",
)
_FIRST_NAMES: tuple[str, ...] = (
"Alex", "Jordan", "Casey", "Morgan", "Taylor", "Riley", "Cameron",
"Sam", "Jamie", "Quinn", "Avery", "Drew", "Reese", "Skyler", "Logan",
"Hayden", "Parker", "Rowan", "Sage", "Emerson", "Finley", "Harper",
"Kai", "Micah", "Nico", "Phoenix", "River", "Shawn", "Sutton", "Wren",
)
_LAST_NAMES: tuple[str, ...] = (
"Reed", "Hayes", "Brooks", "Bennett", "Carter", "Foster", "Gray",
"Hughes", "Jensen", "Knight", "Lambert", "Mason", "Nash", "Owens",
"Porter", "Quincy", "Reyes", "Sutton", "Tate", "Underwood", "Vance",
"Walsh", "Yates", "Zimmerman", "Abbott", "Barker", "Cohen", "Dixon",
"Ellis", "Fitch",
)
def random_first_name() -> str:
return secrets.choice(_FIRST_NAMES)
def random_last_name() -> str:
return secrets.choice(_LAST_NAMES)
def random_full_name() -> tuple[str, str]:
return random_first_name(), random_last_name()
def random_username(first: str = "", last: str = "") -> str:
"""Generate a plausible-looking username.
If a name is provided we mix it with digits; otherwise we use two short
name fragments. Always lowercase, always 8-18 chars.
"""
f = (first or random_first_name()).lower()
l = (last or random_last_name()).lower()
seps = ("", ".", "_", "")
sep = secrets.choice(seps)
digits = "".join(secrets.choice(string.digits) for _ in range(secrets.choice([2, 3, 4])))
style = secrets.randbelow(4)
if style == 0:
base = f + sep + l + digits
elif style == 1:
base = f + digits
elif style == 2:
base = l + sep + f[0] + digits
else:
base = f[0] + sep + l + digits
base = base.strip(".-_")
return base[:18]
@dataclass
class SignupDraft:
site_key: str = "google"
custom_url: str = ""
email: str = ""
password: str = ""
username: str = ""
first_name: str = ""
last_name: str = ""
notes: str = ""
@dataclass
class AccountRecord:
id: str
site: str
url: str
email: str
password: str
username: str = ""
first_name: str = ""
last_name: str = ""
exit_ip: str = ""
exit_hop: str = ""
notes: str = ""
status: str = "created" # pending | created
created_at: str = ""
def accounts_path() -> Path:
return app_data_dir() / "signup_accounts.json"
def draft_path() -> Path:
return app_data_dir() / "signup_draft.json"
def generate_password(length: int = 18) -> str:
n = max(12, min(64, int(length)))
alphabet = string.ascii_letters + string.digits + "!@#$%^&*-_=+"
while True:
pw = "".join(secrets.choice(alphabet) for _ in range(n))
if (
any(c.islower() for c in pw)
and any(c.isupper() for c in pw)
and any(c.isdigit() for c in pw)
):
return pw
def preset_for(key: str) -> SignupSitePreset | None:
return SIGNUP_PRESETS.get(key)
def resolve_signup_url(draft: SignupDraft) -> str:
if draft.site_key == "custom":
return (draft.custom_url or "").strip()
p = preset_for(draft.site_key)
return p.url if p else ""
def load_draft() -> SignupDraft:
p = draft_path()
if not p.is_file():
return SignupDraft()
try:
raw = json.loads(p.read_text(encoding="utf-8"))
return SignupDraft(**{k: raw.get(k, "") for k in SignupDraft.__dataclass_fields__})
except (OSError, json.JSONDecodeError, TypeError):
return SignupDraft()
def save_draft(draft: SignupDraft) -> None:
draft_path().write_text(json.dumps(asdict(draft), indent=2), encoding="utf-8")
def load_accounts() -> list[AccountRecord]:
p = accounts_path()
if not p.is_file():
return []
try:
raw = json.loads(p.read_text(encoding="utf-8"))
if not isinstance(raw, list):
return []
out: list[AccountRecord] = []
for item in raw:
if not isinstance(item, dict):
continue
fields = AccountRecord.__dataclass_fields__
kwargs = {k: item.get(k, "") for k in fields}
if not kwargs.get("id"):
kwargs["id"] = datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S%f")
out.append(AccountRecord(**kwargs))
return out
except (OSError, json.JSONDecodeError, TypeError):
return []
def save_accounts(rows: list[AccountRecord]) -> None:
accounts_path().write_text(
json.dumps([asdict(r) for r in rows], indent=2),
encoding="utf-8",
)
def append_account(record: AccountRecord) -> None:
rows = load_accounts()
rows.insert(0, record)
save_accounts(rows)
def build_autofill_config(draft: SignupDraft) -> dict[str, Any]:
preset = preset_for(draft.site_key)
hosts = list(preset.hosts) if preset else []
selectors: dict[str, list[str]] = {}
if preset:
selectors.update(preset.extra_selectors)
fields: dict[str, str] = {}
if draft.first_name:
fields["first_name"] = draft.first_name
if draft.last_name:
fields["last_name"] = draft.last_name
if draft.username:
fields["username"] = draft.username
if draft.email:
fields["email"] = draft.email
if draft.password:
fields["password"] = draft.password
fields["password_confirm"] = draft.password
out_fields: dict[str, Any] = {}
for key, val in fields.items():
out_fields[key] = val
sel_key = f"{key}_selectors"
if key in selectors:
out_fields[sel_key] = selectors[key]
return {
"site": draft.site_key,
"hosts": hosts,
"fields": out_fields,
"updated_at": datetime.now(timezone.utc).isoformat(),
}
def _extensions_json_entry(ext_dir_name: str) -> dict[str, Any]:
now = int(datetime.now(timezone.utc).timestamp()) * 1000
return {
"id": _EXT_ID,
"syncGUID": str(uuid.uuid4()),
"version": "1.0.0",
"type": "extension",
"location": {
"appProfile": {
"root": "app-profile",
"path": f"extensions/{ext_dir_name}",
}
},
"internalName": None,
"updateURL": None,
"webExtensionConverted": False,
"webExtensionId": _EXT_ID,
"active": True,
"userDisabled": False,
"appDisabled": False,
"installDate": now,
"scope": 1,
}
def install_signup_extension(profile_dir: Path, draft: SignupDraft) -> None:
"""Copy autofill WebExtension into the Firefox profile and write current field values."""
profile_dir.mkdir(parents=True, exist_ok=True)
ext_name = _EXT_ID
dest = profile_dir / "extensions" / ext_name
if dest.exists():
shutil.rmtree(dest, ignore_errors=True)
shutil.copytree(_ext_src_path(), dest)
cfg = build_autofill_config(draft)
(dest / "autofill_config.json").write_text(json.dumps(cfg, indent=2), encoding="utf-8")
ext_json_path = profile_dir / "extensions.json"
entry = _extensions_json_entry(ext_name)
data: dict[str, Any]
if ext_json_path.is_file():
try:
data = json.loads(ext_json_path.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
data = {"schemaVersion": 35, "addons": []}
else:
data = {"schemaVersion": 35, "addons": []}
addons = data.get("addons")
if not isinstance(addons, list):
addons = []
addons = [a for a in addons if isinstance(a, dict) and a.get("id") != _EXT_ID]
addons.append(entry)
data["addons"] = addons
ext_json_path.write_text(json.dumps(data, indent=2), encoding="utf-8")