autoreg-skills-max: initial public release (restored, redacted, hardened)

This commit is contained in:
baradok60915d52e8425
2026-10-20 02:16:32 +03:00
commit f58eafeb48
565 changed files with 60064 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
# Camoufox Registration Patterns (2026-08-10)
## OctoCaptcha Form Validation (`_wait_form_valid`)
GitHub signup has two independent gates keeping the submit button disabled:
1. Field auto-checks (email validity, username, password)
2. OctoCaptcha (DataDome iframe) — hidden `octocaptcha-token` stays empty until verification finishes
Must wait for BOTH before clicking submit. JS evaluation to probe state:
```python
def wait_form_valid(page, timeout_s=60):
"""Wait for GitHub's async field validation AND octocaptcha to resolve."""
deadline = time.time() + timeout_s
while time.time() < deadline:
state = page.evaluate("""() => {
const out = {email_taken: false, errors: [], pending: false,
fields_valid: false, captcha_pending: false,
submit_enabled: false};
const fields = [
['email', 'input[name="user[email]"], #email'],
['username', 'input[name="user[login]"], #login'],
['password', 'input[name="user[password]"], #password'],
];
let successCount = 0, total = 0;
for (const [label, sel] of fields) {
const el = document.querySelector(sel);
if (!el) continue;
total += 1;
if (el.getAttribute('aria-invalid') === 'true') {
const container = el.closest('auto-check') || el.parentElement;
const errEl = container ? container.querySelector('.error') : null;
const txt = errEl ? errEl.textContent.trim().replace(/\\s+/g,' ') : (label + ' invalid');
out.errors.push(label + ': ' + txt.slice(0, 160));
if (/already associated|already been taken|already taken/i.test(txt))
out.email_taken = true;
} else if (el.classList.contains('is-autocheck-successful')) {
successCount += 1;
}
if (el.classList.contains('is-autocheck-in-progress')) out.pending = true;
}
out.fields_valid = (total > 0 && successCount === total && out.errors.length === 0);
const tok = document.querySelector('input[name="octocaptcha-token"], .js-octocaptcha-token');
const tokenVal = tok ? (tok.value || '').trim() : '';
const spinner = document.querySelector('.js-octocaptcha-spinner');
const success = document.querySelector('.js-octocaptcha-success');
out.captcha_pending = (!!tok && !tokenVal && !success.classList.contains('d-none') && !!spinner && !spinner.classList.contains('d-none'));
const email = document.querySelector('input[name="user[email]"], #email');
const form = email ? email.closest('form') : null;
if (form) {
const b = Array.from(form.querySelectorAll('button, input[type=submit]'))
.find(x => (x.getAttribute('name')==='commit') ||
/create account/i.test(x.textContent||x.value||''));
if (b) out.submit_enabled = !b.disabled;
}
return out;
}""")
if state.get("submit_enabled") and not state.get("pending"):
return {"ready": True}
time.sleep(1)
return {"ready": False, "timeout": True}
```
## Proper Click (NOT social buttons)
GitHub's signup page has BOTH email-signup submit buttons AND social-login buttons ("Continue with Google/Apple"). A naive `page.click("button:has-text('Create account')")` may click the wrong button.
Use JS evaluation to find the right button:
```python
page.evaluate("""() => {
const email = document.querySelector('input[name="user[email]"], #email');
const form = email.closest('form');
const act = (form.getAttribute('action') || '').toLowerCase();
// Skip social/OAuth forms
if (act.includes('/sessions/social/') || act.includes('oauth')) return 'social_form';
const btns = Array.from(form.querySelectorAll('button, input[type=submit]'));
const good = btns.find(b => {
const t = (b.textContent || b.value || '').toLowerCase();
const prov = b.getAttribute('data-provider') || '';
if (prov) return false; // skip social buttons
return t.includes('create') || t.includes('account') || b.getAttribute('name') === 'commit';
});
if (good) { good.click(); return 'clicked'; }
return 'no_button';
}""")
```
## Navigation Wait (post-submit)
After clicking submit, the page navigates from `/signup` to `/account_verifications`. The navigation can be slow via proxies (30+ seconds). Instead of polling `page.url` (which is a live property — see pitfall), use `page.wait_for_selector`:
```python
try:
page.wait_for_selector('input[name="launch_code[]"]', state="visible", timeout=90000)
# On verification page
except:
# Check for rate limit or unexpected redirect
body = page.inner_text("body")[:500]
if "rate limit" in body.lower():
return "rate_limited"
```
## Camoufox Launch Options
```python
launch_opts = {"headless": False, "geoip": True}
if proxy_config:
launch_opts["proxy"] = {
"server": proxy["server"],
"username": proxy["username"],
"password": proxy["password"],
}
with Camoufox(**launch_opts) as browser:
...
```
`geoip=True` is recommended when using proxies to avoid `LeakWarning`.
## Mail.tm API
```python
base = "https://api.mail.tm"
# Create account
r = requests.post(f"{base}/accounts", json={"address": f"{username}@{domain}", "password": pw})
token = [REDACTED]"{base}/token", json={"address": email, "password": pw}).json()["token"]
# Poll inbox
headers = {"Authorization": f"Bearer {token}"}
r = requests.get(f"{base}/messages", headers=headers)
```
## Pitfalls from this session
### `page.url` is a LIVE property
`page.url` re-evaluates every time it's accessed. Between `log(f"URL: {page.url}")` and `if "account_verif" in page.url:`, the page can navigate.
**Fix:** read once into a local variable.
### Post-OTP "accepted" without session
After OTP, GitHub may redirect to root path (`/` or `/dashboard`) making it look like success, but the account actually needs a post-signup login.
**Fix:** after "accepted" result, navigate to `/settings/profile`. If redirected to `/login`, do the post-signup login.
### Rate limit persistence
GitHub secondary rate limits can persist for **hours**, not minutes. A 15-minute wait is often insufficient. Rotate IPs aggressively.
### Proxy pool dead (2026-08-10)
bpproxy.at residential pool — ALL 10 proxies dead. curl returns empty response, Camoufox returns `NS_ERROR_PROXY_CONNECTION_REFUSED`. Proxies are not rate-limited — they are physically unreachable. Always verify proxy alive before use:
```bash
curl -sI --max-time 10 -x <proxy_url> https://github.com | head -1
```
If no `HTTP/1.1 200` response, proxy is dead — don't use it.
### Camoufox proxy config format
Playwright REQUIRES credentials in separate `username`/`password` keys:
```python
from core.proxy_utils import playwright_proxy_config
# Input: "http://user:pass@host:1000"
# Output: {"server": "http://host:1000", "username": "user", "password": "pass"}
```
Embedding credentials in the `server` field yields `NS_ERROR_PROXY_CONNECTION_REFUSED`.
### mail.tm domains blocked (2026-08-10)
GitHub silently rejects accounts registered with mail.tm domains (`emalupe.com`). OTP is accepted, but account returns 404 on API check. Use t-online.de IMAP pool instead.

View File

@@ -0,0 +1,182 @@
# CDP Approach — system Chrome via DevTools Protocol
When Playwright's bundled Chromium crashes on Windows (exit code 2147483651 = STATUS_BREAKPOINT), use the system Chrome via CDP instead of `launch_persistent_context`.
## Why this works
Playwright's `chromium-1208` (bundled with playwright 1.58.0) crashes on Windows on `launch_persistent_context`. The system Chrome (151.0.7922.109) works fine when launched manually with `--remote-debugging-port=9222`.
## Launch Chrome
```bash
"C:\Program Files\Google\Chrome\Application\chrome.exe" \
--remote-debugging-port=9222 \
--user-data-dir="C:/Users/User/gh_work/chrome_profile" \
--no-sandbox --disable-gpu --disable-software-rasterizer \
--disable-features=Crashpad --no-first-run --no-default-browser-check
```
Key: `--user-data-dir` must point to the existing warmed-up profile.
## CDP Control Script
Control via websocket + `Runtime.evaluate`:
```python
import json, urllib.request
from websocket import create_connection
CDP = "http://127.0.0.1:9222"
# Create new tab
req = urllib.request.Request(f"{CDP}/json/new?url=about:blank", method="PUT")
data = json.loads(urllib.request.urlopen(req).read())
ws = create_connection(data["webSocketDebuggerUrl"], timeout=15)
# Evaluate JS
ws.send(json.dumps({
"id": 1, "method": "Runtime.evaluate",
"params": {"expression": "document.title", "returnByValue": True}
}))
# Navigate
ws.send(json.dumps({
"id": 2, "method": "Page.navigate",
"params": {"url": "https://github.com/signup"}
}))
# Close tab
urllib.request.urlopen(f"{CDP}/json/close/{tab_id}")
```
## Python 3.14 isolated mode
Hermes venv pollutes `sys.path` with playwright paths that cause `PermissionError`. Use `python -I`:
```bash
/c/Users/User/AppData/Local/Python/pythoncore-3.14-64/python.exe -I script.py
```
## UTF-8 encoding fix
Windows console uses cp1252. Force UTF-8 at script start:
```python
import sys, io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
```
## Verified
- 2026-08-10: Chrome 151 on CDP port 9222, registration flow works through CDP
- Python 3.14.3 with `-I` flag, playwright import is clean
## Critical: Path + Full URL Matching (BOTH required)
GitHub's signup flow uses `return_to=` query params extensively. The verification page URL is:
`https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Faccount_verifications`
**Path alone is `/login` — which does NOT contain "account_verif".** Must check BOTH:
```python
from urllib.parse import urlparse
def get_path(url_str):
return urlparse(url_str).path.rstrip("/")
path = get_path(url)
url_lower = url.lower()
# still_on_verify: check BOTH path and full URL
still_on_verify = ("account_verif" in path and "launch" not in path) or \
("account_verif" in url_lower and "launch" not in url_lower)
```
**Time-limit still_on_verify:** in the post-OTP loop, only skip for first 10s of the 30s window:
```python
deadline = time.time() + 30
while time.time() < deadline:
url = str(cdp_eval(cdp, "window.location.href")[0])
path = get_path(url)
url_lower = url.lower()
still_on_verify = ("account_verif" in path ...) or ("account_verif" in url_lower ...)
if still_on_verify and time.time() < deadline - 20: # first 10s only
time.sleep(1)
continue
# After 10s, evaluate other signals:
# - has_session cookie?
# - on /dashboard or /welcome? → SUCCESS
# - on /login with success flash? → needs login
# - code error message? → failure
```
This applies to ALL CDP URL checks:
- After submit: check BOTH `path` and full URL for `account_verif` or `launch`
- Post-OTP: time-limit the still_on_verify check, then evaluate other signals
- Rate limit detection: check page body text, not URL
## Rate Limit Behavior (2026-08-10)
- Secondary rate limit persists 1+ hour on same IP
- 15-minute wait is insufficient
- Fields on rate-limited page: all `querySelector` return null (email, password, login all `false`)
- Page stays on `/signup` but form is effectively dead
- **Must rotate IP** — waiting on same IP is wasted time
## OctoCaptcha Lock — Accounts Born-Dead (2026-08-10)
GitHub signup form has `<input name="octocaptcha-token" required>` AND honeypot fields (`required_field_*`). Submitting without the token:
- Account IS created (GitHub sends verification email)
- Account is immediately flagged/locked
- Login fails with "Incorrect username or password" even through real Chrome
- Verified: manual browser login with just-created credentials also fails
Solution: solve octocaptcha before form submission, or use a pre-warmed profile that bypasses the captcha entirely.
## CDP Debug Pattern
Dump page state via `Runtime.evaluate` to diagnose form issues:
```javascript
JSON.stringify({
title: document.title,
url: window.location.href,
fields: Array.from(document.querySelectorAll('input')).map(el =>
el.id + '|' + (el.name||'') + '|' + (el.type||'') + '|' + (el.getAttribute('inputmode')||'')
),
body: document.body ? document.body.innerText.substring(0, 300) : ''
})
```
Log before and after each form submission to trace what GitHub actually sees vs what the script filled.
## `requestSubmit()` vs `submit()`
`form.submit()` bypasses JavaScript event handlers (including octocaptcha validation, CSRF token injection, and anti-bot checks). Use:
```javascript
form.requestSubmit ? form.requestSubmit() : form.submit()
```
`requestSubmit()` triggers proper HTML5 form validation and all event handlers, same as a real user click.
## `Runtime.evaluate` Message Format
```python
cdp["ws"].send(json.dumps({
"id": cdp["id"],
"method": "Runtime.evaluate",
"params": {
"expression": code,
"returnByValue": True, # return JSON, not RemoteObject
"awaitPromise": True # wait for async completion
}
}))
```
Without `awaitPromise: true`, the call returns immediately without waiting for expression completion. With `returnByValue: true`, the result is a plain JSON value instead of a RemoteObject reference.

View File

@@ -0,0 +1,106 @@
# DataDome Slider, Wall Taxonomy, Proxy Pool Ops, Bank Audit
Lessons from gh-regkit-prime mass registration runs (Camoufox + residential/free proxies + Gmail aliases).
## DataDome wall taxonomy — classify BEFORE choosing a strategy
Four distinct walls; each needs a different response. Misclassifying burns minutes per account:
| Wall | Signature | Response |
|------|-----------|----------|
| Slide-to-verify | `geo.captcha-delivery.com` iframe, `div.sliderContainer` | Solve by drag (below) |
| Robot wall | `captcha__robot` class or "Access is temporarily restricted" INSIDE the iframe | Unsolvable — fail fast, disable proxy session, rotate |
| JS-challenge wall | Blank page, only text "Please enable JS and disable any ad blocker" | Survives reloads on flagged sessions — fail fast, disable session |
| SSL-broken proxy | Firefox renders "Secure Connection Failed / SSL received a record that exceeded the maximum permissible length" AS A PAGE (no exception) | Proxy is dead — disable session; never reload, it never recovers |
Critical: hard-block marker checks must read the IFRAME too (`frame.inner_text('body')` + `frame.content()`), not just the main page — the robot wall lives only inside the challenge frame. Keep all marker strings in one module-level tuple so every check path shares it.
## DataDome slide-to-verify solver (current generation)
Detection: iterate `page.frames`, match `"captcha-delivery" in frame.url` OR `frame.url == "about:srcdoc"` — the widget is often in a NESTED srcdoc frame. Scan all frames, not just captcha-delivery URLs. A poke/click loop on the frame body NEVER solves a slider — cap pokes per page URL (~3) or the repeated body clicks escalate the challenge.
Real DOM (from live frame dump — earlier guessed selectors `div.slider_button`, `[id*='slider']` DO NOT EXIST):
```html
<div class="sliderContainer">
<div class="sliderTarget"><i class="sliderTargetIcon"></i></div>
<div class="slider"><i class="sliderIcon"></i></div>
</div>
```
Handle selectors: `div.sliderContainer div.slider`, fallback `div.slider`. Drag distance = `sliderTarget` bounding-box position (NOT frame/body width). Audio verification (`captcha__audio__button`) is the plan-B path.
When the handle isn't found, dump the frame HTML ONCE to `debug_stall/ddframe_dump.html` and read the real selectors from it — never guess captcha DOM.
Drag that works: `mouse.move` to handle center ±2px jitter → pause → `mouse.down()` → 22-34 eased-curve steps (`0.5-0.5*cos(pi*frac)`) toward target + overshoot 6-14px, per-step y jitter, 6-22ms sleeps → correct back 3-6 small steps → creep forward → release EXACTLY at the right edge (an undershoot after correction silently fails validation). Keep classic checkbox selectors as fallback after the slider attempt.
After a rejected drag the widget flips to a RETRY state: click `text=/RETRY/i` inside the frame for a fresh challenge, budget shared with pokes (≤3 total), then fail fast.
Hook the solver into EVERY place that clicks Continue / waits for the next wizard step — the slider can gate the email→password transition silently (URL stays `/signup`, no error). The DataDome overlay also HIDES the Continue button, so probe for the challenge independently of Continue-button visibility (every ~3s in the wait loop).
## Wizard-stall debug artifacts (build this FIRST)
When a multi-step signup says "next step did not appear", blind retry loops waste hours. Add to the stall path: save `screenshot + [f.url for f in page.frames] + page text[:4000]` into `debug_stall/` with timestamp, and log the filename. Then classify from the artifact:
- frames contain `captcha-delivery`/srcdoc widget → slider flow (above)
- page text has SSL/JS-wall/robot markers → proxy dead → disable + rotate
- no captcha frame, URL bounced to homepage → soft session reset = bad-reputation IP → disable proxy + rotate, don't retry same exit
- Arkose frame present → voting vision solver
The stall error message logs only ~200 chars of body — the full artifact is what you actually read. Vision-analyze the screenshot when text markers are ambiguous.
## Residential sticky-session pool ops
- ALL sticky sessions share ONE host:port (only the username `sess<N>` differs). A rotator `disable` by host:port therefore COMMENTS THE ENTIRE POOL → runner falls back to direct/single proxy → GitHub secondary rate limit within minutes. Disable ONLY by exact full URL (scheme+creds+host:port); add an ambiguity guard in the rotator that refuses host:port disable when it matches >1 authed line.
- Residential quality is MIXED: some "residential" sessions exit via datacenter IPs → instant robot wall. Fail-fast + exact-URL disable filters them out within ~30s per bad session; good sessions complete full cycles in 2-4 min.
- Residential latency shifts every timing assumption: verify-page redirect takes 10-30s; auto-login cookie needs a 75s wait (30s starves it and accounts get saved verified-only, losing repo/2FA/PAT stages).
## Patient verify-wait after launch code (anti false-reject)
A state-machine that returns "verify page stable" after ~4s will FALSELY reject good codes on slow residential exits — the redirect simply hasn't happened yet. Correct pattern:
1. Wait up to 90s for the page to leave verify.
2. Declare "rejected" ONLY when GitHub shows an explicit error banner (`incorrect`, `invalid code`, `doesn't work`, `wrong code`, `try again` in page text) or the full timeout expires.
3. At the halfway point, re-type the code once if the input boxes are still visible — the 8-box auto-submit sometimes misses the last box's event.
4. Propagate block/rate-limit/cancel exceptions; swallow only the plain "never stabilized" timeout and keep waiting.
## Parallel batches without IP collisions
The runner loads its config ONCE at startup and accepts `--config <file>`, and reads the proxy file per account. To scale to N parallel batches: copy config to `config_b.json` with `proxy_file=proxies_b.txt` and write a DISJOINT session range into that pool file (e.g. A=sess900-999, B=sess1000-1099). Disjoint sticky sessions = distinct exit IPs = no GitHub cross-flagging. Never run two batches off the same pool file — they can pick the same session concurrently.
Restart cost discipline: killing a batch mid-flow loses the in-flight account, and FAILED accounts never persist their generated password → unrecoverable (email burned). Batch code fixes together and restart once; verify the live pid + CreationDate via CIM before killing (heartbeats from already-killed batches keep arriving as stale buffers — match timestamps before believing them).
## Account bank audit — how to count "alive" honestly
Anonymous `GET api.github.com/users/<login>` hits the 60/hr limit fast and 403 is NOT evidence of death. Correct order:
1. Parse all `accounts/*.txt` bank files (`email----pass----username----totp----flag----pat`), dedupe by email keeping the most complete record.
2. **Definitive check = each account's own PAT**: `GET /user` with `Authorization: Bearer <pat>`. 200 → alive (compare `login` to stored username; check `suspended_at`). 401 → pat dead. Use one live PAT as auth header for bulk `/users/<login>` existence checks of the rest.
3. Accounts WITHOUT a PAT: probe `https://github.com/<username>` HTML (404 → gone). Registrations that never reached the PAT stage get deleted by GitHub at ~100% rate — "alive" bank = full-cycle accounts (2FA+repo+PAT) only. Report total bank rows vs PAT-verified alive — never equate them.
4. Save a JSON report (`AUDIT_ALIVE.json`) with alive list (email/login/totp/pat) so the next audit is incremental.
## Free-proxy pool maintenance loop
Free pools rot within hours. Reseed recipe (validated):
1. Source: local proxy DB (`proxy_bot/proxies.db`, table `pool WHERE alive=1`) → normalize to `http://ip:port`.
2. Test every candidate with a raw `CONNECT github.com:443` socket check (accept only `200`) — generic HTTP-alive is not enough; ~80% fail the GitHub CONNECT.
3. Backup current `proxies.txt`, merge survivors + new alive, dedupe.
4. **Restart the rotator after rewriting proxies.txt** — it holds the pool in memory; the batch re-reads the file per account but the rotator's `/disable` and forward port serve the stale list.
5. Zombie check after crashes/kills: stale `camoufox.exe` processes + leftover `SkeletonUILock-*` files in `%LOCALAPPDATA%/camoufox/camoufox/` cause `BrowserType.launch: Timeout 180000ms`. Kill camoufox older than the current batch and `rm -rf SkeletonUILock-*` before relaunching.
## PAT backfill for saved-no-PAT accounts (password-reset path, validated end-to-end)
Accounts saved without a PAT (killed mid-flow, PAT-page TLS error, auto-login timeout) are recoverable via GitHub password reset + Gmail donor IMAP:
1. `GET /password_reset` → fill email → submit (generic success page either way).
2. Pull the reset link from IMAP. **Search `TO "<alias-suffix>"` FIRST** — on a donor mailbox shared with live batches, UNSEEN/SINCE searches drown in hundreds of launch-code emails and time out; the TO-header search returns a tiny exact set.
3. **Fetch with `BODY.PEEK[]`, never `RFC822`** — RFC822 sets `\Seen` and eats launch codes that concurrently running batches are still polling for.
4. Open the link → fill new password → submit → login form fallback (session is often NOT auto-established after reset) → create PAT through the UI (`/settings/tokens/new`) in the same fresh session (fresh login rarely triggers the sudo gate).
5. **2FA gate on the reset link** — if the account has TOTP enabled, the link renders `input[name='app_otp']` (placeholder `XXXXXX OR XXXXX-XXXXX`, form action `/password_reset/<token>/2fa`) INSTEAD of the password form; symptom if unhandled is a bare "reset form fields not found". Wait ~8s for the otp input; fill pyotp TOTP from the stored secret (strip spaces) or fall back to an unused `XXXXX-XXXXX` recovery code from the per-email recovery file. The 6-digit input auto-submits (`js-verification-code-input-auto-submit`); recovery codes need a submit click. Wait for the otp input to disappear, THEN wait for the password field. Accounts without 2FA go straight to the password form. The subsequent login fallback can ALSO prompt a 2FA code — reuse the same TOTP path.
6. Write the full account line to a results file and dequeue only on SUCCESS — a GIVE-UP account must stay in the queue for the next run.
Page-load pitfalls on free/slow proxies (each cost a full give-up cycle before being fixed):
- Both reset forms (`/password_reset` email field AND post-link password field) need `wait_for_selector(state='visible', timeout=12-15s)` per candidate selector — a bare `sleep(2)` + fill misses the late render and burns the attempt.
- Flaky proxies MITM TLS and Firefox renders a **cert-error page as real content**: title `Warning: Security Risk`, `aboutNetError`, `certerror`, `chrome://` stylesheet links. Treat those strings as block markers → fail fast, rotate proxy; and on any unknown 'fields not found' dump `page.content()` to a debug file — guessing from the error string alone misdiagnosed this twice. Read the dump with `grep -o '<title>[^<]*</title>'` + `grep -o '<input[^>]*>'` + `grep -o '<form[^>]*>'`: a correct title with zero password inputs means a GATE (2FA), not a render failure — this is exactly how the app_otp gate was found.
- GitHub throttles repeated password-reset emails per account (~5+ requests/day → "no reset email in IMAP" loops with correct TO-search). Park such accounts for ≥1h instead of burning retries; the throttle is on the account, not the proxy.
- Every new reset request invalidates the previous link; if two backfill runs overlap on one queue they burn each other's links ("no reset email" / dead-link loops). Before relaunching a backfill, list python processes via CIM and kill the real child pid — the terminal-tool background pid is the bash wrapper; taskkill on it orphans the python child that keeps processing the queue.
- Batch-kill filters must exclude the dashboard process: gh-regkit-prime runs `main.py --host 127.0.0.1 --port 8000` (web UI) alongside batches `main.py --count N` / `--config config_b.json` — match on `--count`/`--config`, not on `main.py`.
- A batch python can die SILENTLY (print to a dead tee pipe = unprintable traceback): the log just stops mid-wave with no error. Liveness = CIM process list, not the absence of an exit notification; on silent death just relaunch.
## Heredoc pitfall when patching runner code
Patching a Python file via bash heredoc that itself contains `\n` inside f-strings breaks (the escape lands literally and corrupts the source, `ast.parse` fails). Write the patch script with `write_file` to a `.py` file and run it — and always `ast.parse(src)` before writing back. Never leave the batch on old code after a runner.py change: Python keeps the old module in memory, so kill + relaunch the batch after every fix.

View File

@@ -0,0 +1,50 @@
# mail.tm Utilities — disposable email backend for GitHub AutoReg
mail.tm API предоставляет временные почтовые ящики без регистрации. GitHub принимает письма на mail.tm-домены (web-library.net, и др.).
## API Reference
Base URL: `https://api.mail.tm`
### create_email()
```python
from src.mailtm_utils import create_email
mail = create_email()
# {
# "email": "ghx8s5czsqqfzu@web-library.net",
# "password": "shuzMXH4bj1P6Awf9wrA...",
# "token": "eyJ0eXAiOiJKV1QiLCJh..."
# }
```
Флоу:
1. `GET /domains` — получить доступный домен
2. `POST /accounts` — создать ящик (`{address, password}`)
3. `POST /token` — получить JWT токен (`{address, password}`)
### wait_for_code(token, timeout=120)
```python
code = wait_for_code(mail["token"], timeout=120)
# "848413" — 6-8 digit verification code
```
Флоу:
1. `GET /messages` — поллить входящие (Authorization: Bearer token)
2. Фильтровать по `from.address` содержит `github.com`
3. `GET /messages/{id}` — прочитать тело письма
4. Regex `\b(\d{6,8})\b` — извлечь код подтверждения
## Проверено
- 2026-07-27: `nova9417` создан через mail.tm (домен web-library.net)
- GitHub отправил verification code, код успешно извлечён и применён
## Питфолы
- **Домен может меняться** — всегда вызывать `GET /domains` перед созданием, не хардкодить
- **Письма могут задержаться** — GitHub отправляет письмо не мгновенно, timeout=120 покрывает
- **Токен истекает** — mail.tm JWT живёт ограниченное время, создавать новый ящик для каждой регистрации
- **Не использовать для входа** — ящики одноразовые, после регистрации email может понадобиться для 2FA/восстановления

View File

@@ -0,0 +1,47 @@
# OctoCaptcha — второй слой защиты GitHub
Обнаружено 2026-07-29. GitHub использует ДВА слоя DataDome:
## Слой 1: DataDome на странице
- URL: `/signup` → редирект на `geo.captcha-delivery.com`
- Решается через SOCKS5 + не-headless Chrome
- Время: 3-6 секунд
- Кука: `datadome` для `github.com`
## Слой 2: OctoCaptcha в iframe
- URL iframe: `https://octocaptcha.com/datadome?origin_page=github_signup_redesign`
- Появляется после ввода email и нажатия Continue
- Iframe изначально скрыт (`class="v-hidden"`, `display:none`)
- JS-ключ: `window.ddjskey = "2CF892E038EE5D8217D27787467ED2"`
- Опции: `window.ddoptions = { ajaxListenerPath: true }`
- Скрипт: `https://js.datadome.co/tags.js`
- Токен: `<input name="octocaptcha-token">` (скрыт, заполняется JS)
## Поведение
- При открытии OctoCaptcha URL напрямую — DataDome кука ставится мгновенно (браузер уже прогрет с GitHub)
- В контексте iframe — `tags.js` загружается, но **challenge не завершается**
- Iframe `visible=false` даже после `style.display='block' !important` (GitHub JS переопределяет)
- Поле `octocaptcha-token` остаётся пустым 60+ секунд
- Без токена форма возвращает `200` с редиректом обратно на `/signup`
## Что пробовали
| Метод | Результат |
|-------|-----------|
| `fetch()` POST в обход формы | status=200, url=`/signup` — аккаунт НЕ создан |
| Мобильный user-agent | DataDome блокирует страницу |
| `iframe.style` = visible | GitHub JS переопределяет, iframe остаётся `visible=false` |
| Click/scroll/mousemove в область формы | Без эффекта |
| Открыть OctoCaptcha URL в отдельной вкладке | Кука ставится, но iframe на GitHub не видит |
## Рабочее решение
**Прогретый Chrome-профиль** (с куками, историей посещений GitHub) — обходит ОБА слоя:
1. DataDome на `/signup` не показывается (браузер уже «свой»)
2. OctoCaptcha iframe не загружается или сразу решается
Без профиля регистрация **невозможна** по состоянию на 2026-07-29.

View File

@@ -0,0 +1,145 @@
# OTP Entry & Post-Registration Provisioning
## OTP Entry: Multi-Strategy with Fallback
GitHub's verification page has changed over time. The current flow (2026-08-10) uses a **single numeric input**, NOT the old `launch_code[]` digit boxes. Use 3 strategies in order:
### Strategy 1: launch_code[] digit boxes (LEGACY — GitHub pre-2026-08)
```html
<input name="launch_code[]" type="number" maxlength="1"> × 8
```
If found (8 boxes), fill each individually and submit the form.
### Strategy 2: Single numeric input (CURRENT — works 2026-08-10)
```javascript
var e = document.querySelector('input[inputmode="numeric"]') ||
document.querySelector('input[aria-label*="code" i]') ||
document.querySelector('input[aria-label*="verif" i]') ||
document.querySelector('input[type="text"]:not([name="login"]):not([name="password"])');
e.value = code;
e.dispatchEvent(new Event('input', {bubbles: true}));
e.dispatchEvent(new Event('change', {bubbles: true}));
// Submit: form.requestSubmit() or click button[type="submit"]
```
### Strategy 3: Brute force (last resort)
Type code into ANY visible input and click any button matching verify/continue/submit/confirm.
### Wait for input to appear
The verification page may render inputs dynamically. Wait up to 10s:
```python
for _ in range(20):
any_input = document.querySelectorAll('input:not([type="hidden"]):not([type="submit"])').length
if any_input > 0: break
time.sleep(0.5)
```
## Post-OTP Result Detection
After OTP submission, GitHub can redirect to **multiple states**. Use **BOTH path and full URL** for matching:
| URL Pattern | Path | Meaning | Action |
|-------------|------|---------|--------|
| `/dashboard`, `/welcome`, `/` | match | Account created + auto-logged-in | Extract session cookies |
| `/login?return_to=...account_verifications` | `/login` | Still on verification page (OTP pending/failed) | Check for error messages after 10s |
| `/login` with flash "account was created successfully" | `/login` | Account created, needs login | Do post-signup login |
| `/login` without flash | `/login` | Registration failed or redirected | Abort |
**Critical: `/login?return_to=.../account_verifications` is the verification page.** The path is `/login`, but the full URL contains `account_verif`. Check BOTH:
```python
path = urlparse(url).path.rstrip("/")
url_lower = url.lower()
# still_on_verify: check BOTH path and full URL
still_on_verify = ("account_verif" in path and "launch" not in path) or \
("account_verif" in url_lower and "launch" not in url_lower)
```
**Time-limit the still_on_verify check:** in the post-OTP loop, only skip for first 10s of the 30s window. After that, evaluate other signals (has_session, dashboard, login, code error):
```python
deadline = time.time() + 30
while time.time() < deadline:
# ... get URL, path, cookies ...
if still_on_verify and time.time() < deadline - 20: # first 10s only
time.sleep(1)
continue
# ... check other signals ...
```
## Post-Signup Login
If GitHub shows "Your account was created successfully! Please sign in to continue":
1. Navigate to `https://github.com/login`
2. Fill `#login_field` (email) and `#password` (password)
3. Click submit button
4. Navigate to `https://github.com/settings/profile` to verify authenticated
5. Extract `user_session` cookie
## Session Verification
```python
page.goto("https://github.com/settings/profile")
# If redirected to /login → NOT authenticated
# If stays on /settings/profile → AUTHENTICATED
```
## 2FA + PAT via provision.py
After registration, use the `provision.py` from `авторег проект/backend/platforms/github/provision.py` for post-processing.
**WARNING: HTTP provisioning is rate-limited on the same IP.** If the browser registration used the local IP, the HTTP `requests`-based login to the fresh account triggers "login failed: rate". To avoid this:
- Use the SAME proxy for provisioning as registration, OR
- Extract cookies from the browser session and inject them into `requests.Session`, OR
- Do 2FA + PAT through the browser (CDP) instead of HTTP
### enable_2fa(email, password) → {totp_secret, recovery_codes}
Pure HTTP (requests + pyotp), no browser needed. Flow:
1. GET `/login` → extract CSRF
2. POST `/session` → authenticate
3. GET `/settings/two_factor_authentication/setup/intro` → extract scoped CSRF
4. POST `/settings/two_factor_authentication/setup/initiate` (type=app) → get secret + recovery codes
5. POST `/settings/two_factor_authentication/setup/verify` (type=app, otp=TOTP)
6. POST `/settings/two_factor_authentication/setup/enable`
### generate_token(email, password, totp_secret, scopes) → ghp_/github_pat_...
Creates a classic PAT. Flow:
1. Login with 2FA
2. Handle sudo re-auth if needed
3. GET `/settings/tokens/new` → extract CSRF
4. POST `/settings/tokens` with scopes
5. Extract `ghp_` or `github_pat_` from response
## Rate Limiting
GitHub applies a **secondary rate limit** after 2-3 fast signups. Symptoms:
- Form fields not found (all `querySelector` return null — `{"email":false,"password":false,"login":false}`)
- Page stays on `/signup` after submit
- HTTP login returns "login failed: rate"
**Mitigation:**
- Wait 10-15 minutes between batches
- Use proxy rotation (SOCKS5 from residential pool)
- Rate limit on same IP persists **1+ hour** — 15-minute wait is insufficient
- HTTP provisioning (requests) hits the SAME rate limit as browser — use same proxy or browser cookies
## Verified
- **2026-08-10**: `launch_code[]` boxes NO LONGER present on GitHub verification page. Single `input[inputmode="numeric"]` strategy works (S2).
- **2026-08-10**: Multi-strategy OTP entry (S1→S2→S3) with explicit logging confirmed working
- **2026-08-10**: Post-OTP `still_on_verify` check must be time-limited (10s max) — otherwise catches `/login?return_to=...account_verifications` indefinitely
- **2026-08-10**: Both path AND full URL needed for verification page detection (path alone misses `/login?return_to=...account_verifications`)
- **2026-08-10**: HTTP provisioning rate-limited on local IP (`login failed: rate`). Must use same proxy as browser or inject browser cookies.
- provision.py verified working (2026-08-06, from `авторег проект` backend)

View File

@@ -0,0 +1,88 @@
# ZTE Proxy + Task API Reference
## ZTE Modem Commands
```
Modem: 192.168.0.1 (ZTE MF935 4G)
Mobile proxy: 127.0.0.1:8899
Login: SHA256(SHA256(password).upper() + LD).upper()
# Get LD
curl "http://192.168.0.1/goform/goform_get_cmd_process?cmd=LD&multi_data=1"
# Login (password=admin)
curl -X POST "http://192.168.0.1/goform/goform_set_cmd_process" \
-d "isTest=false&goformId=LOGIN&password=<HASH>"
# Rotate IP (unreliable — returns failure)
curl -X POST "http://192.168.0.1/goform/goform_set_cmd_process" \
-d "isTest=false&goformId=DISCONNECT_NETWORK"
sleep 10
curl -X POST "http://192.168.0.1/goform/goform_set_cmd_process" \
-d "isTest=false&goformId=CONNECT_NETWORK"
```
## ENI AUTOREG v3.0 Task API
```
POST http://127.0.0.1:8002/api/tasks
{
"platform": "github",
"action": "register",
"count": 5,
"config": {
"executor_type": "headed", // "protocol" default — always override
"captcha_solver": "yescaptcha_api",
"proxy": "http://127.0.0.1:8899", // browser proxy
"extra": {
"use_proxy": "0", // disable bpproxy pool
"identity_provider": "mailbox"
}
}
}
```
## Check Task Status
```
GET http://127.0.0.1:8002/api/tasks/<task_id>
GET http://127.0.0.1:8002/api/tasks/<task_id>/events?limit=100
POST http://127.0.0.1:8002/api/tasks/<task_id>/cancel
```
## Mailbox Provider Management
```
# Get current providers
GET http://127.0.0.1:8002/api/providers/mailbox
# Update IMAP pool
PUT http://127.0.0.1:8002/api/providers/mailbox/imap_pool
{
"provider_type": "mailbox",
"provider_key": "imap_pool",
"enabled": true,
"is_default": true,
"config": {
"imap_pool_file": "data/working_mails_live.txt",
"imap_host": "imap.t-online.de",
"imap_ssl": "true",
"imap_pool_allow_reuse": "false"
}
}
```
## DataDome Solver Chain (Camoufox)
Camoufox = Firefox UA → CapSolver rejects (Chrome UA required)
2Captcha = only Firefox DataDome solver → needs balance
YesCaptcha = no DataDome support
`t=bv` in DataDome `dd` object → IP banned → rotate, don't waste solver quota
`t=fe` → solvable challenge → send to 2Captcha
## Account Output Format
DB: id, platform, email, password, user_id, token, status=registered
2FA file: email:password:[REDACTED]