Files
autoreg-skills-max/skills/windows-browser-automation/references/cloudflare-turnstile-limitations.md

4.6 KiB

Cloudflare Turnstile — automation barriers

Target: https://lovable.dev/signup (Lovable.dev registration page) Turnstile sitekey: 0x4AAAAAAAHfngZVf0MDFkKR Turnstile mode: auto (Cloudflare decides whether to challenge) Widget action: signup-email-password

Verified blockers

1. CDP-level detection (primary blocker)

Cloudflare detects the Chrome DevTools Protocol connection itself. This is NOT removable via stealth patches:

// This does NOT bypass Turnstile detection
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });

Evidence: Even when connecting to the user's REAL Chrome (not Playwright's Chromium) with navigator.webdriver=undefined, the Turnstile iframe shows auto_timeout in the URL path:

/auto/fbE/auto_timeout/normal?lang=auto

2. window.turnstile.execute() returns empty

// window.turnstile IS available with execute() method
window.turnstile.execute('cf-chl-widget-XXXXX');
// Returns success, but token remains empty
window.turnstile.getResponse('cf-chl-widget-XXXXX');  // → ''

Even the programmatic API refuses to return a token when CDP is connected.

3. 2captcha ERROR_CAPTCHA_UNSOLVABLE

2captcha Turnstile solving works by visiting the target URL in a worker's browser. Since Lovable's Turnstile is in auto_timeout for ALL automated browsers, the workers also see auto_timeout and cannot solve it.

2captcha returns: ERROR_CAPTCHA_UNSOLVABLE (after 15-20s attempt) 2captcha balance: $7.03 (sufficient, not the issue)

4. turnstile-signup endpoint accepts token but Firebase blocking function rejects

Lovable has a server endpoint /api/turnstile-signup that validates the Turnstile token with Cloudflare. Even when this endpoint returns 204 (token accepted), the Firebase Cloud Function (BLOCKING_FUNCTION_ERROR_RESPONSE) rejects the signup with:

error: BLOCKING_FUNCTION_ERROR_RESPONSE (internal-error)

The blocking function checks Turnstile token validity plus IP match. If the token was solved from a different IP (2captcha's IP), the function rejects even if the token itself is valid.

What was tested and failed

Approach Tools Result
Headless Playwright Playwright chromium iframe empty, auto_timeout
Headed Playwright + stealth --disable-blink-features=AutomationControlled iframe empty
Playwright + real Chrome channel='chrome' + user data dir iframe renders, but auto_timeout
Playwright + real Chrome + init script add_init_script overriding webdriver webdriver=undefined confirmed, but still auto_timeout
rebrowser_playwright from rebrowser_playwright import async_playwright iframe empty
Headed natural mouse page.mouse.move(steps=N) + page.mouse.click() checkbox visible, click → failure_retry
Chrome DevTools MCP CDP Input.dispatchMouseEvent via DevTools tools checkbox visible, click → failure_retry
window.turnstile.execute() programmatic execution returns success, token empty
2captcha Turnstile solving service ERROR_CAPTCHA_UNSOLVABLE
2captcha + curl_cffi solve + proxy ERROR_CAPTCHA_UNSOLVABLE
pyautogui physical click via Windows SendInput NOT FULLY TESTED (window focus issue)

What MIGHT work (untested)

  • pyautogui — physical mouse/keyboard via Windows SendInput API. Chrome must be launched WITHOUT --remote-debugging-port. Cloudflare sees real human input events and cannot distinguish them.
  • User clicking once — always works but defeats the purpose of automation.
  • OAuth (Google/GitHub) — bypasses Turnstile entirely. GitHub OAuth opens popup window; GitHub login page may have its own anti-bot (DataDome CAPTCHA blocked CDP Chrome in tests).

Turnstile detection signals (probable)

Based on what failed and what partially worked:

  1. CDP WebSocket connection — Chrome DevTools Protocol uses a WebSocket connection. Cloudflare can detect the presence of this connection.
  2. navigator.webdriver — standard check (trivial to spoof, not the primary signal)
  3. Chrome automation flags — --enable-automation adds a info bar and changes navigator.webdriver. Playwright passes --disable-blink-features=AutomationControlled to suppress this.
  4. Input event patterns — programmatic Input.dispatchMouseEvent has different timing/coordinate patterns than human input
  5. window.chrome.runtime presence/absence — automation tools often modify this

pyautogui approach (last resort)

See main SKILL.md section "pyautogui — coordinate-based GUI automation" for the full pattern.