turnstile-bypass: solving Cloudflare Turnstile and waiting-room challenges with headed Chrome
Cross-platform Cloudflare Turnstile solver (macOS, Windows, Linux)
At a glance
- What is it?
- turnstile-bypass drives headed Chrome through Cloudflare Turnstile widgets and waiting-room interstitials, then returns a token or a cf_clearance cookie as a single JSON object. It stops at IP bans, rate limits and headless mode.
- Who is it for?
- Adopt turnstile-bypass if your blocker is a Turnstile widget or a Cloudflare waiting-room interstitial and you can run headed Chrome, or xvfb on a server. Skip it if the response you are getting is 1020, 1015, or a Bot Fight rejection, because those sit outside what this repository claims to handle.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 10 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 16, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
The two Cloudflare layers it targets
Cloudflare puts more than one kind of challenge in front of a browser, and turnstile-bypass separates the two that a scripted Chrome session actually meets. The first is the Turnstile widget embedded in the page you want to use, with the login form at aipaycards.com given as the example. Solving that produces a token string the site itself reads. The second is the interstitial waiting room, the "Just a moment" screen that stands between a request and the origin, with grok.com given as the example. There the useful output is not a token but a cf_clearance cookie plus the real origin page.
The README states that a bare curl to grok.com returns 403 with a cf-mitigated: challenge header, and that the same Chrome tab shows the Grok app after a solve with --fresh. Keeping the two cases apart matters, because their failure modes differ: a widget that never yields a token and a waiting room that never clears point at different causes.
How solve.py picks a lane
scripts/solve.py is the entry point, and it chooses between three routes before it touches a page. The default lane after install.py runs is DrissionPage plus the packaged Chrome extension. If agent-browser-cli and Node are already on PATH, solve.py prefers that lane because the README describes it as faster; setting TURNSTILE_PREFER_AB=0 forces the DrissionPage path instead. The third lane is YesCaptcha, reachable with --lane yescaptcha and a --sitekey argument.
The choice has a mechanical consequence. Iframe clicks have to go through Chrome CDP on port 19221, not the shim on 19222, and the extension exists to make those clicks look like a real pointer. The --lane ab route is faster only when agent-browser-cli and Node are already installed and that Chrome already carries the extension.
The Manifest V3 extension and the screenX patch
The extension lives in assets/turnstilePatch/ as unpacked source, with assets/turnstilePatch.zip as the packed copy you can rebuild with python3 scripts/pack_extension.py. It is Manifest V3, runs in the MAIN world, applies to all frames, and matches https://challenges.cloudflare.com/* only.
What it does with that access is narrow: it patches MouseEvent.screenX and MouseEvent.screenY. The README explains why. Chrome CDP clicks set screen coordinates equal to client coordinates, a behaviour tracked as chromium issue 40280325, and Turnstile reads that equality as a bot signal. Faking the screen coordinates removes the tell.
DrissionPage loads the extension itself through add_extension, so the default solve.py path never asks you to open chrome://extensions. The manual route still exists for anyone driving their own Chrome: Developer mode, Load unpacked, select assets/turnstilePatch/.
Installing it and running a first solve
Install needs Python 3.10 or newer and Google Chrome or Chromium.
git clone https://github.com/Sophomoresty/turnstile-bypass.git
cd turnstile-bypass
python3 scripts/install.pyinstall.py creates a .venv inside the repo, installs requirements.txt (DrissionPage), packs assets/turnstilePatch.zip, and runs scripts/preflight.py. The preflight output you want is:
{ "ok": true, "methods": { "drissionpage": true } }If Chrome is not found, the README lists per-OS fixes: install Google Chrome on macOS, set CHROME_PATH on Windows, install google-chrome-stable or chromium on Linux. The macOS form it gives is export CHROME_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome". On a Linux box with no desktop, install xvfb and prefix commands with xvfb-run -a.
The manual install spells the same steps out:
python3 -m venv .venv
.venv/bin/python -m pip install -r requirements.txt
python3 scripts/pack_extension.py
python3 scripts/preflight.pyThen point it at a page:
python3 scripts/solve.py --url "https://aipaycards.com/login"
python3 scripts/solve.py --url "https://grok.com/" --freshStdout is a single JSON object. The --fresh flag starts from a clean Chrome profile rather than reusing one, which is what the grok.com example uses.
Reading the JSON that comes back
Widget success means "ok": true with a token longer than 20 characters, and the README tells you to use it immediately because the TTL is around 300 seconds. Waiting-room success is "ok": true with kind set to cf_clearance or cf_passed and clearanceLen above 20, and the tab ends up on the real site. Failure is "ok": false with an error field, and the instruction is to stop rather than fabricate a token.
The Verified table in the README records what the maintainer saw on macOS with Chrome 152 and agent-browser over CDP 19221. A dummy Turnstile at demo.turnstile.workers.dev returned a 21-character token in 3.46s, the local examples/interactive-dummy.html file returned 21 characters in 7.46s, and the production widget on aipaycards.com/login returned an 816-character token three times out of three in 8 to 11 seconds. The grok.com interstitial produced kind=cf_clearance with clearanceLen between 533 and 597 in about 9 seconds with --fresh. Treat those as the repository's own record, not as a number you should expect to reproduce on other hardware.
A live two-page check exists as scripts/e2e.py, which exits 0 only on success:
python3 scripts/solve.py --url "https://grok.com/" --fresh
python3 scripts/e2e.pyWhere it stops working
The repository carries an explicit out-of-scope table, and it is the most useful part of the README. Turnstile widgets on the origin page and interstitial challenges are in scope. Cloudflare 1020 access denied, 1015 rate limiting, WAF blocks, Bot Fight once this Chrome profile is already banned, hCaptcha, reCAPTCHA and headless Chrome are all out.
Two of the stated limits are about the environment rather than the code. Headed Chrome is required, and the interstitial path calls Page.bringToFront because waiting-room JavaScript often refuses to finish while document.visibilityState is hidden. Datacenter IPs often fail; the documented policy is one residential-proxy retry and then stop. Tokens must not be cached across sessions.
That scope explains the shape of the project. It is a challenge helper for a browser a site still considers welcome, not a fingerprint browser, and nothing in it addresses a target that has already decided your IP is unwelcome.
YesCaptcha and Camoufox, the two other routes in the repo
If local Chrome is the wrong answer for your situation, the repository already contains two other paths. scripts/solve_yescaptcha.py is the YesCaptcha lane: you supply YESCAPTCHA_CLIENT_KEY and pass a sitekey, and a third-party service returns the solution instead of your own browser passing the challenge.
python3 scripts/solve.py --lane yescaptcha --url "https://example.com" --sitekey "0x..."The trade is direct. You give up the local browser, and with it the need for a visible tab and a Chrome binary, in exchange for sending the challenge to someone else's workers and paying per solve. The README calls it a last resort.
scripts/camoufox_turnstile.py takes a different approach again, replacing the Chrome plus extension stack with the Camoufox browser. The README does not document this path in the same detail as the DrissionPage lane, so treat it as a starting point rather than a supported default.
Maintenance, licence and what to check first
The repository is MIT licensed and its only runtime dependency is DrissionPage, listed in requirements.txt. That is a small surface, and the whole install is one script. The last push was on 2026-09-07, close enough to the present that the default lane should still match current Chrome builds, though the Chromium behaviour the extension works around is the kind of thing a browser update can change without notice.
Check three things before you build on it. Confirm your blocker is one of the two in-scope layers and not a 1020 or 1015 response. Confirm you have a headed Chrome, or xvfb on a server, because headless is out of scope. Confirm the token TTL fits your flow, since the README warns against caching tokens across sessions. It also ships AGENTS.md, a short runbook aimed at coding agents, which suggests the maintainer expects the caller to be a script rather than a person.
Editorial conclusion
Adopt turnstile-bypass if your blocker is a Turnstile widget or a Cloudflare waiting-room interstitial and you can run headed Chrome, or xvfb on a server. Skip it if the response you are getting is 1020, 1015, or a Bot Fight rejection, because those sit outside what this repository claims to handle. Verify the lane before you trust a result: run scripts/preflight.py and confirm drissionpage is true, then point solve.py at a dummy page such as demo.turnstile.workers.dev before anything you care about.
Frequently asked questions
How do you solve a Cloudflare Turnstile challenge with turnstile-bypass?
Install with python3 scripts/install.py, then run python3 scripts/solve.py with the --url of the page holding the widget. Success returns "ok": true and a token longer than 20 characters that you use immediately, since the TTL is around 300 seconds.
Can Cloudflare access be bypassed with turnstile-bypass?
Only two layers are in scope: the Turnstile widget on the origin page and the interstitial waiting room that yields a cf_clearance cookie. Cloudflare 1020 access denied, 1015 rate limiting, WAF blocks and Bot Fight on an already banned Chrome profile are all listed as out of scope.
How does Cloudflare Turnstile detect a bot, according to this repository?
One signal it uses is pointer geometry. Chrome CDP clicks set screen coordinates equal to client coordinates, tracked as chromium issue 40280325, and Turnstile reads that equality as a bot. The bundled extension patches MouseEvent.screenX and screenY so the coordinates stop matching.
Community notes