lilpacy.infoJA
How Cloudflare Turnstile Spots Bots: A Look at CAPTCHA-less Verification

How Cloudflare Turnstile Spots Bots: A Look at CAPTCHA-less Verification


TL;DR

Cloudflare Turnstile detects bots without requiring any user interaction, as a replacement for CAPTCHA. It runs four background challenges (Proof-of-Work, Proof-of-Space, Web API probes, behavioral analysis), combines them via machine learning, and issues a token. Automated control via CDP (Chrome DevTools Protocol) is blocked instantly via environment fingerprinting, and recovering within the same session is difficult (though officially, retry and reset mechanisms do exist).

The Trigger: Hitting a Wall on Form Auto-Fill

I was auto-filling a web form via Chrome DevTools MCP and the Submit button stayed disabled forever. When I forcibly ran btn.disabled = false in JS and clicked, I got “Cloudflare Turnstile verification failed.” Reload, manual click — nothing worked, and I couldn’t submit again from that browser session ever.

Why are automation tools detected? How does Turnstile work in the first place? When I dug in, the defense turned out to be more layered than I’d imagined.

What Was Wrong With CAPTCHA?

Traditional CAPTCHA had humans prove themselves by solving visual puzzles like “read these distorted letters” or “select the traffic lights.” The problems are clear.

Cloudflare’s answer was “make the user do nothing in the first place.”

Turnstile’s Four-Layer Challenges

Turnstile runs invisible challenges simultaneously, in parallel. The difficulty automatically adjusts to the visitor’s risk level.

Note: The challenge composition below is based on technical analysis from reverse-engineering work; Cloudflare doesn’t publicly disclose internal implementation details. Implementations can change without notice (information as of February 2026).

flowchart LR
    V["Visitor opens page"] --> W["Turnstile JS<br/>widget loads"]
    W --> C1["Proof-of-Work"]
    W --> C2["Proof-of-Space"]
    W --> C3["Web API probe"]
    W --> C4["Behavioral analysis"]
    C1 --> ML["ML model<br/>risk judgment"]
    C2 --> ML
    C3 --> ML
    C4 --> ML
    ML -->|low risk| T["Issue token"]
    ML -->|high risk| B["Block / further verification"]

1. Proof-of-Work (computational proof)

Have the browser solve a small computational task. The point isn’t to measure CPU load — it’s to impose a computational cost.

The principle is the same as Bitcoin mining: pose a problem like “find a nonce such that the leading N bits of SHA-256(nonce + challenge) are zero.” The browser has to brute-force the nonce (solving is O(2^N)), but server-side verification only needs one hash (O(1)). This asymmetry is the heart of the design.

For legitimate users, the difficulty N is set low enough that it finishes in milliseconds to a few hundred milliseconds. But if a bot farm runs 10,000 sessions in parallel, it needs 10,000 PoW computations, and CPU cost piles up linearly. It’s an economic deterrent: “cheap on a single machine, doesn’t pay off at scale.” Secondarily, an unusually fast solve time can serve as a clue that “this is being solved on something other than a browser, on high-performance hardware.”

2. Proof-of-Space (memory proof)

Where Proof-of-Work imposes “CPU time” on bots, Proof-of-Space imposes “memory.” Cloudflare disclosed the existence of this challenge type when announcing Turnstile but hasn’t disclosed specific algorithms or memory sizes.

What’s known is that the browser is made to allocate memory and perform a computation tied to that allocation. An amount of memory that’s no problem for a regular browser becomes problematic for a bot farm running thousands of sessions in parallel on a single server, since each session needs memory — putting a physical cap on parallelism.

About terminology: Academically, “Proof-of-Space” usually refers to disk storage proofs (Ateniese et al., Dziembowski et al.). What Cloudflare calls “Proof-of-Space” in Turnstile is a RAM allocation challenge, distinct from the cryptography literature context.

3. Web API Probes

This is the cleverest part. Whether the browser is real is judged by the behavior of the browser’s APIs.

Concretely, signals like the following are observed.

  • Canvas/WebGL fingerprinting: outputs of identical drawing commands differ subtly per browser/GPU/driver combination
  • Consistency of the navigator object: is navigator.webdriver set to true? Is navigator.plugins an empty array?
  • Performance API timings: do page-load timing patterns match human operation?
  • CDP detection: traces of DevTools Protocol commands like Runtime.enable or Page.enable having been executed

The last item is what tripped me up. Operating a page via the Chrome DevTools Protocol leaves detectable traces in the browser’s runtime. Behavioral changes in window.chrome.csi, format differences in Error.stack, binding objects DevTools injects — multiple signals are used to detect CDP control.

4. Behavioral Analysis

Mouse movements, scroll patterns, key-press timings, and page-transition patterns are analyzed comprehensively. Human operation has its own distinctive “noise.” Nobody clicks at perfectly even intervals, and no mouse trajectory is a perfectly straight line.

The key here is that judgment isn’t made on a single signal. All challenge results are fed into an ML model, and risk is judged on the composite score.

Token Issuance and Verification Flow

When a challenge passes, Turnstile issues a JWT-like verification token. This token is sent to the server when the form is submitted.

sequenceDiagram
    participant B as Browser
    participant T as Turnstile JS
    participant CF as Cloudflare
    participant S as Site server

    B->>T: Page load (init with Sitekey)
    T->>CF: Fetch challenge
    CF-->>T: Deliver challenge
    T->>T: Run 4-layer challenges
    T->>CF: Send challenge results
    CF-->>T: Issue verification token
    Note over T: Set token in hidden input
    B->>S: Submit form (with token)
    S->>CF: Verify token via Siteverify API
    CF-->>S: Verification result (success/failure)
    S-->>B: Response

Server-side verification is done by a POST request to https://challenges.cloudflare.com/turnstile/v0/siteverify. Send the Secret Key and the token, and it verifies validity, issuance time, and host-name match.

Tokens have an expiration. With the Pre-Clearance feature you can reuse via cookies within a session, but basically tokens are one-shot.

The Three Widget Modes

Turnstile provides three modes for different use cases.

ModeVisible to user?InteractionUse case
ManagedShown according to riskCheckbox only when neededGeneral (recommended)
Non-interactiveInvisibleNoneUX-first
InvisibleInvisibleNoneFully hidden

Managed is the most flexible — Cloudflare automatically judges risk and only shows a checkbox when something looks suspicious. The vast majority of users pass through without seeing anything.

Why Can’t a CDP-Controlled Browser Recover?

What was most interesting this time was this behavior: in my environment, once a browser session was connected via CDP, neither reload nor switching to manual operation got it past Turnstile’s challenge.

Note: The following is based on observations from my macOS + Chrome + Chrome DevTools MCP environment. Behavior may differ depending on browser version, OS, or Turnstile-side updates. Cloudflare’s official docs describe automatic challenge retry and the turnstile.reset() reset mechanism, so a recovery path does exist in theory.

Plausible reasons:

  1. Detection of browser launch flags: starting Chrome with flags like --remote-debugging-port introduces detectable changes into the browser’s runtime environment
  2. Session-level trust score: Turnstile likely manages trust per session rather than per page. Sessions judged as “automation environments” tend to keep a low trust score
  3. Preconditions for the Turnstile token: token issuance presupposes “challenge results from a trustworthy environment.” If the environment’s trust score is low, even correct challenge results don’t easily produce a token
stateDiagram-v2
    [*] --> Clean: Normal browser launch
    [*] --> Tainted: Launched with CDP
    Clean --> Verified: Pass challenge
    Verified --> TokenIssued: Token issued
    Tainted --> Blocked: Challenge rejected
    Blocked --> Blocked: Reload/manual operation (no recovery in my environment)
    Blocked --> Retry: turnstile.reset() (officially retryable)
    Retry --> Blocked: Re-fails if environment signals don't change

In my environment, the strategy “fill in automatically and then click Submit manually” didn’t work. That doesn’t mean Turnstile categorically forbids recovery — rather, the environment signals under CDP control kept causing the challenge to fail.

Turnstile From the Implementer’s Side

Let’s also look at it from the perspective of integrating Turnstile into a website. The implementation is surprisingly simple.

Client side is one script tag and one div element.

<script src="https://challenges.cloudflare.com/turnstile/v0/api.js" async defer></script>
<div class="cf-turnstile" data-sitekey="0x4AAAAAAA..."></div>

Server side is just token verification.

import requests

def verify_turnstile(token: str, secret_key: str) -> bool:
    resp = requests.post(
        "https://challenges.cloudflare.com/turnstile/v0/siteverify",
        data={"secret": secret_key, "response": token}
    )
    return resp.json().get("success", False)

For SPAs, call turnstile.render() explicitly.

const widgetId = turnstile.render('#turnstile-container', {
    sitekey: '0x4AAAAAAA...',
    callback: (token) => {
        // Include the token in form data on submit
        document.getElementById('cf-token').value = token;
    },
});

That’s all it takes for the four-layer challenges to run behind the scenes. It works without going through Cloudflare’s network, so you can keep your existing infrastructure as-is.

Comparison with CAPTCHA

AspectTraditional CAPTCHATurnstile
User actionSolve a puzzleNone (in most cases)
Judgment methodPuzzle correctnessComposite environment + behavior score
Bot resistanceBypassable via image recognitionHigh resistance from layered signals
AccessibilityDifficult for visually impairedNo problem since no operation is needed
PrivacyOften used for ad trackingDoesn’t collect tracking data
Impact on drop-offLargeMinimal

Conclusion

Turnstile’s design philosophy is clear: shift “proving you’re human” from being a user burden to a system responsibility.

The multi-layer defense combining four challenges (Proof-of-Work, Proof-of-Space, Web API probes, behavioral analysis) doesn’t depend on a single signal, so it’s robust. And the design where a session once judged as an “untrustworthy environment” doesn’t recover structurally prevents incremental breakthroughs by automation tools.

Personally, what struck me most was the stickiness of CDP detection. The fact that “automate the input, then submit manually” didn’t work (at least in my environment) is because the boundary between bot and human is drawn at “environment” rather than “operation.” Although retry and reset mechanisms exist officially, as long as the environment signal itself is tainted, recovery was effectively difficult.

By the way, I ended up just opening the form in normal Safari and copy-pasting from a text file. A good chance to feel the limits of automation firsthand.

That’s all.

References