autoreg-skills-max: initial public release (restored, redacted, hardened)
This commit is contained in:
@@ -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.
|
||||
182
skills/github-autoreg/references/cdp-approach.md
Normal file
182
skills/github-autoreg/references/cdp-approach.md
Normal 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.
|
||||
@@ -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.
|
||||
50
skills/github-autoreg/references/mailtm-utils.md
Normal file
50
skills/github-autoreg/references/mailtm-utils.md
Normal 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/восстановления
|
||||
47
skills/github-autoreg/references/octocaptcha-datadome.md
Normal file
47
skills/github-autoreg/references/octocaptcha-datadome.md
Normal 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.
|
||||
145
skills/github-autoreg/references/otp-and-provisioning.md
Normal file
145
skills/github-autoreg/references/otp-and-provisioning.md
Normal 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)
|
||||
88
skills/github-autoreg/references/zte-proxy-task-api.md
Normal file
88
skills/github-autoreg/references/zte-proxy-task-api.md
Normal 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]
|
||||
Reference in New Issue
Block a user