Files
autoreg-skills-max/skills/github-autoreg/references/datadome-slider-stall-debug-bank-audit.md

13 KiB

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):

<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.