9.1 KiB
GitHub Autoreg — Email Domain Blocklist & Working Providers
Captured 2026-08-10 from extended autoreg testing with CDP + Camoufox.
Blocked Disposable Domains
GitHub silently rejects accounts registered with these domains. The signup form accepts the OTP, GitHub redirects to the home page (github.com/), but the account never exists (API returns 404). No error message, no warning — the rejection is silent.
| Domain | Provider | Status |
|---|---|---|
emalupe.com |
mail.tm | ❌ Silently blocked |
*.mail.tm |
mail.tm (all domains) | ❌ Likely blocked |
Confirmed Working Providers
| Domain | Provider | Notes |
|---|---|---|
t-online.de |
Telekom Germany | 212 accounts created 6-7 Aug 2026, all alive. IMAP-based mailbox. Requires t_online.py provider from авторег проект/backend/providers/mailbox/. 17,872 emails in data/working_mails_live.txt. |
Detection: How to Tell if Account Was Created
After OTP acceptance, the account may or may not exist:
import urllib.request, json
url = f"https://api.github.com/users/{username}"
req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
resp = urllib.request.urlopen(req, timeout=10)
data = json.loads(resp.read())
if "login" in data:
print(f"LIVE: {username} (created: {data.get('created_at')})")
else:
print(f"DEAD: {username} — 404 Not Found")
Key insight: pending_login status in the CDP script means "OTP was accepted but session not confirmed" — NOT that the account exists. Always verify with the GitHub API before assuming the account was created.
Pattern: Silent Rejection Flow
- Signup form submitted →
account_verificationspage - OTP code entered → form accepted
- GitHub redirects to
https://github.com/(home page) - No
user_sessioncookie set - Navigate to
/settings/profile→ redirected to/login - GitHub API:
404 Not Found
The account was never created. The OTP form accepts the code but the server-side validation rejects the signup silently.
Camoufox OTP Submit
The launch-code form uses form[data-target="launch-code.form"]. The correct way to submit after filling individual launch_code[] boxes:
# Fill each box
for i, digit in enumerate(code):
try:
boxes[i].click(timeout=2000) # focus
except Exception:
pass
boxes[i].fill(digit)
time.sleep(0.08)
time.sleep(0.6)
# Submit via JS (Playwright's click may fail on disabled button)
page.evaluate("""
const f = document.querySelector('form[data-target="launch-code.form"]');
if (f) f.requestSubmit();
""")
requestSubmit() is preferred over form.submit() because it triggers the form's submit event handlers and validation.
Python 3.11 Isolation from Hermes Venv
When running py -3.11, the hermes venv's site-packages (C:\Users\User\AppData\Local\hermes\hermes-agent-0.18.2-latest\venv\Lib\site-packages\) can leak into sys.path, causing:
PermissionError: [Errno 13] Permission deniedon playwright imports- Wrong versions of packages being loaded
Fix: Set PYTHONPATH= to empty before running, or use -I flag:
PYTHONPATH= py -3.11 main.py
# or use -I (isolated mode):
py -3.11 -I script.py
ENI AUTOREG v3.0 Backend
- Location:
C:\Users\User\Desktop\авторег проект\backend\ - Start:
cd backend && PYTHONPATH= py -3.11 main.py(runs on port 8002) - CRITICAL:
PYTHONPATH=is mandatory — without it, hermes-venv playwright leaks into Python 3.11's sys.path causingPermissionError - API: FastAPI with
/api/tasksendpoint for task creation - Task API:
POST /api/taskswith{"platform":"github","action":"register","count":N,"config":{...}} - GitHub platform:
browser_registercapability via Camoufox (supported executors:headless,headed) - Mailbox:
t-online.devia IMAP pool (17,872 emails indata/working_mails_live.txt). Provider key:imap_pool - DB:
account_manager.db(SQLite) - Provision:
provision.py(2FA + PAT via HTTP, no browser needed) - Warmup:
_warmup_account()inbrowser_register.py(avatars, bio, display name)
Task Config Format
Correct format for headed browser with ZTE proxy:
{
"platform": "github",
"action": "register",
"count": 3,
"config": {
"executor_type": "headed",
"captcha": "yescaptcha_api",
"proxy": "http://127.0.0.1:8899",
"extra": {
"use_proxy": "0"
}
}
}
CRITICAL: use_proxy:"0" MUST be in extra sub-dict, NOT top-level config. The task runtime reads extra.get("use_proxy") (line 252 of services/task_runtime.py). If omitted or placed in the wrong location, the proxy pool's round-robin selection overrides the custom proxy URL.
Default executor: If executor_type is omitted, defaults to "protocol" (HTTP-based), which GitHub doesn't support. Must explicitly set "headed" or "headless".
DataDome Captcha Solver Compatibility
GitHub triggers DataDome challenge on the signup page. Only 2Captcha supports DataDome:
| Solver | DataDome Support | Status (2026-08-12) |
|---|---|---|
2Captcha (twocaptcha_api) |
✅ Yes | Balance: ZERO (ERROR_ZERO_BALANCE) |
YesCaptcha (yescaptcha_api) |
❌ 不支持 DataDome |
— |
CapSolver (capsolver_api) |
❌ 不支持 DataDome |
— |
When DataDome pre-solve fails, the browser continues to the signup form but may encounter additional challenges. Without a working DataDome solver, the email input may not become visible within timeout.
IMAP Pool Provider Config (Pitfall)
When updating the imap_pool provider via PUT /api/providers/mailbox/imap_pool, the config dict REPLACES the existing config entirely. Always include the full config:
{
"provider_type": "mailbox",
"provider_key": "imap_pool",
"is_default": true,
"config": {
"imap_pool_file": "data/working_mails_live.txt",
"imap_host": "secureimap.t-online.de",
"imap_port": 993,
"imap_ssl": true,
"imap_pool_allow_reuse": "true"
}
}
Sending "config":{} wipes all fields and the pool fails to load emails.
Proxy Pool Status
| Proxy | Type | Status | Date |
|---|---|---|---|
| bpproxy.at (all) | residential + datacenter | ❌ ALL DEAD — 10/10 proxies return empty response, curl exits 0 but no HTTP headers | 2026-08-10 |
residential-x.bpproxy.at:1000 |
residential hardsession | ❌ Dead | 2026-08-10 |
datacenter-x.bpproxy.at:2000 |
datacenter hardsession | ❌ Dead | 2026-08-10 |
ZTE mobile proxy (127.0.0.1:8899) |
4G mobile (Vodafone UA) | ✅ WORKS — browser reaches GitHub, DataDome pre-solve triggers | 2026-08-12 |
ZTE Mobile Proxy (2026-08-12)
The ZTE MF935 4G modem provides a mobile IP that bypasses GitHub's proxy blocking. The mobile_proxy.py in backend/platforms/android_gmail/ creates a local HTTP/CONNECT proxy on port 8899 that binds to the ZTE interface (192.168.0.4).
How to use:
- ZTE modem must be connected (check:
curl http://192.168.0.1→ redirect to index.html) - Mobile proxy must be running on port 8899 (check:
netstat -ano | grep ":8899") - Task config:
{"executor_type":"headed","captcha":"yescaptcha_api","proxy":"http://127.0.0.1:8899","extra":{"use_proxy":"0"}} - CRITICAL:
use_proxy:"0"MUST be inextrasub-dict, not top-level config. Without it, the proxy pool overrides the custom proxy URL.
Network topology:
- ZTE interface:
192.168.0.4→ gateway192.168.0.1 - Default route: WiFi
192.168.50.1(metric 55), ZTE192.168.0.1(metric 10000 — backup only) - Mobile proxy forces upstream through
192.168.0.4viasocket.bind((src_ip, 0))
Proxy format: `http://bpuser-XXX:[REDACTED-EMAIL]:[REDACTED]
Playwright/Camoufox proxy format: Must use playwright_proxy_config() from core/proxy_utils.py — splits URL into {server, username, password}. Embedding credentials in the server field causes NS_ERROR_PROXY_CONNECTION_REFUSED.
Testing proxies: Use curl -sI --max-time 10 -x "$URL" https://github.com — must return HTTP/1.1 200 with headers. Empty response = dead proxy.
CDP Sync Client (reg_cdp.py pattern)
The CDP Chrome auto-registration uses a sync (non-async) client with urllib.request + websocket-client. Simpler than the async aiohttp pattern for single-threaded flows:
import json, urllib.request, websocket
def cdp_connect(ws_url):
ws = websocket.create_connection(ws_url, timeout=15)
ws.send(json.dumps({"id":1,"method":"Page.enable"}))
ws.send(json.dumps({"id":2,"method":"Runtime.enable"}))
return ws
def cdp_cmd(ws, method, params=None):
mid = random.randint(10000, 99999)
ws.send(json.dumps({"id":mid,"method":method,"params":params or {}}))
while True:
r = json.loads(ws.recv())
if r.get("id") == mid:
return r.get("result", {})
def cdp_eval(ws, code):
r = cdp_cmd(ws, "Runtime.evaluate", {"expression":code,"returnByValue":True})
return r.get("result",{}).get("value")
# Usage:
ws = cdp_connect("ws://127.0.0.1:9222/devtools/page/XXX")
cdp_eval(ws, "window.location.href")
Chrome launch: chrome.exe --remote-debugging-port=9222 --user-data-dir=C:\Users\User\gh_work\chrome_profile