ChatGPT Won't Let You Type Until Cloudflare Reads Your React State
TL;DR Highlight
A reverse-engineering analysis that decrypts Cloudflare Turnstile's encrypted bytecode to confirm that it inspects not only browser fingerprints but also React app internal state (such as __reactRouterContext) before ChatGPT allows a message to be sent.
Who Should Read
Security developers curious about how bot detection/prevention systems work, or developers building automation tools or scrapers who want to understand the internal workings of Cloudflare Turnstile.
Core Mechanics
- Every time ChatGPT sends a message, it silently runs a Cloudflare Turnstile program in the browser. This program is delivered as freshly encrypted bytecode (28,000-character base64) per request, and user input is blocked until this check completes.
- The encryption is two-layered. The outer layer is XOR-decrypted using the p token from the prepare request, and the inner 19KB blob is XOR-decrypted using a key embedded as a float literal (e.g., 97.35) inside the bytecode. Because the key is included within the payload, full decryption is possible with only the HTTP request/response. The author verified this across all 50 of 50 requests tested.
- The decrypted program is written for a custom VM with 28 opcodes, and register addresses are randomized per request. The author decompiled the Cloudflare SDK source (sdk.js, 1,411 lines) to reverse-engineer the opcodes.
- The 55 attributes collected by the program are divided into three layers. Layer 1 is browser fingerprinting (8 WebGL, 8 screen, 5 hardware, 4 font measurement, 8 DOM, 5 storage attributes). Layer 2 consists of 5 Cloudflare edge headers (city, latitude/longitude, IP, region, etc.) that only exist on requests routed through the Cloudflare network.
- The most notable Layer 3 is a React app state inspection. It checks three values: __reactRouterContext (an internal object attached to the DOM by React Router v6+), loaderData (the result of route loaders), and clientBootstrap (a ChatGPT-specific SSR hydration object). These values only exist if the JavaScript bundle has actually executed and React has fully hydrated.
- As a result, bots that only emulate browser APIs or parse HTML cannot reproduce React internal state and are detected. The author describes this as bot detection at the 'application layer' rather than the browser layer.
- The collected fingerprint is stored in localStorage under the key '6f376b6560133c2c' and persists across page loads. The program concludes with 4 VM instructions that serialize all 55 attributes into JSON to generate a token.
Evidence
- "Nick, an OpenAI Integrity team employee, commented directly on the post, explaining that this check is intended to prevent platform abuse such as bots, scraping, and fraud, and that it aims to concentrate GPU resources on real users to sustain free unauthenticated usage. He also noted that page load time, time-to-first-token, and payload size are monitored, and that the impact is negligible for most users with only a small minority experiencing slight delays. | The author's claim that 'React app state only exists after full hydration' is key to bot detection was countered with the argument 'Does this matter if headless browsers also execute JavaScript?' Since real Chrome headless does run JS bundles, some argued the real purpose is closer to filtering lightweight bot frameworks that only stub browser APIs. | Some noted that running a full Windows 11 VM with Chrome and GPU acceleration enabled would bypass all these checks, and that at approximately $0.01 per 1,000 page loads on AWS, it is affordable enough to be practical. Specific figures were mentioned suggesting that memory page deduplication could allow 50 VMs to run simultaneously. | User experience complaints about Cloudflare were also raised, including excessive CAPTCHA encounters when using Firefox or IPs deemed 'suspicious,' and one user reported ChatGPT tabs freezing after long conversations even on an M5 MacBook Pro. | The 'So what?' problem of the article was also pointed out. Many reacted that OpenAI simply wants to serve users who properly run their React app, questioning what the issue is. While the reverse engineering itself was found interesting, many felt the practical conclusion was unclear, as illustrated by comments like 'Does this enable free API inference?'"
How to Apply
- "If you are building a bot detection system for your own service, adding an application-layer check that sends React app state (__reactRouterContext, loaderData, and other hydration artifacts) to the server to verify that a real SPA has executed can effectively filter lightweight bots that only emulate browser APIs. | If you are considering adopting Cloudflare Turnstile, this analysis helps you understand that Turnstile is not a simple CAPTCHA but a VM-based fingerprinting system that collects 55 attributes, allowing you to weigh user latency and privacy trade-offs before making an adoption decision. | If you need to test ChatGPT or Turnstile-protected sites in automated or E2E test environments, you should run a real Chromium instance with GPU rendering and allow React hydration to complete, rather than using API stubs or lightweight headless mode, in order to pass Turnstile checks. | For security research purposes, if you want to analyze Turnstile bytecode, you can reference the author's publicly shared 5-step decryption chain (p token XOR → float key extraction → inner blob decryption) to fully decrypt the program from HTTP traffic capture alone."
Code Example
snippet
# Turnstile outer layer decryption code shared by the author
import base64, json
# Step 1: Read p_token from the prepare request and dx from the response
# Step 2: Extract the outer bytecode (89 VM instructions) via XOR decryption
outer = json.loads(bytes(
base64.b64decode(dx)[i] ^ p_token[i % len(p_token)]
for i in range(len(base64.b64decode(dx)))
))
# → Array of 89 VM instructions
# Step 3: Find the 5-arg instruction after the inner 19KB blob and use the last argument (float) as the XOR key
# Example: [41.02, 0.3, 22.58, 12.96, 97.35] → key is 97.35
# Step 4: XOR-decrypt the inner blob using str(key) → JSON containing 417–580 VM instructions
inner = json.loads(bytes(
base64.b64decode(blob)[i] ^ ord(str(key)[i % len(str(key))])
for i in range(len(base64.b64decode(blob)))
))
# React app state attributes checked by Turnstile (Layer 3)
# - window.__reactRouterContext (React Router v6+ internal DOM object)
# - loaderData (result of route loaders)
# - clientBootstrap (ChatGPT SSR hydration-specific object)
# Key under which the fingerprint is stored in localStorage
FINGERPRINT_KEY = '6f376b6560133c2c'Terminology
TurnstileCloudflare's bot detection solution. Instead of showing users a CAPTCHA, it silently runs a JavaScript program in the browser to determine whether the visitor is a human or a bot.
hydrationThe process by which, after a server pre-renders HTML (SSR), JavaScript executes on the client and 're-attaches' event handlers and state to that HTML. The React app is not fully functional until this process is complete.
XOR 암호화A simple encryption method that performs a bitwise exclusive OR operation on two pieces of data. XORing twice with the same key restores the original data.
VM bytecodeA set of instructions interpreted by a Virtual Machine. Here it refers to a custom VM using 28 opcodes defined by Cloudflare, employed to make reverse engineering more difficult.
WebGL fingerprintingA technique for identifying a device by reading GPU manufacturer, driver, and renderer information via the WebGL API. Even across identical devices, unique GPU combinations provide high discriminative power.
SSR (Server-Side Rendering)An approach where the server pre-generates HTML and sends it to the client. It is advantageous for SEO and initial load speed, but requires hydration on the client side afterward.