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