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
# 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
Related Papers
What happened after 2k people tried to hack my AI assistant
실제로 6,000개 이상의 이메일로 AI 에이전트에 prompt injection 공격을 시도한 공개 실험 결과로, Claude Opus 4.6이 비밀 파일 유출을 한 번도 허용하지 않았지만 실험 설계의 현실성에 대한 논란이 뜨거웠다.
When Does Combining Language Models Help? A Co-Failure Ceiling on Routing, Voting, and Mixture-of-Agents Across 67 Frontier Models
여러 LLM을 조합해도 '모든 모델이 동시에 틀리는 비율(β)'이 성능 상한선이며, 업계가 쓰는 pairwise 상관계수(ρ)는 이 상한선을 예측하지 못한다.
Beyond Function Calling: Benchmarking Tool-Using Agents under Tool-Environment Unreliability
실제 환경처럼 API가 망가지거나 결과가 이상할 때 LLM 에이전트가 얼마나 잘 버티는지 측정하는 벤치마크 ToolBench-X 공개.
Nearly Half of LG Smart TV Apps Contain Residential Proxy SDKs
6,038개의 LG·Samsung 스마트 TV 앱을 스캔했더니 2,058개에서 사용자의 IP를 몰래 팔아 트래픽을 중계하는 Residential Proxy SDK가 발견됐다. TV는 컴퓨터처럼 감시받지 않아서 프록시 호스트로 거의 이상적인 환경이다.
Prompt Injection as Role Confusion
LLM이 시스템 프롬프트, 사용자 입력, 툴 출력을 구분하지 못하는 구조적 결함이 prompt injection의 근본 원인이라는 ICML 2026 논문으로, 현재 LLM 보안 아키텍처의 한계를 명확히 분석한다.
GPT-5.5 hallucinates 3x more than MIT-licensed GLM-5.2
모델 크기가 커질수록 성능이 좋아진다는 통념에 반해, 오픈소스 753B 모델 GLM-5.2가 추정 1~2T 규모의 GPT-5.5보다 환각 비율이 3배 낮다는 벤치마크 결과가 나왔다. 단순히 파라미터 수와 벤치마크 점수만으로 모델을 선택하면 실제 업무에서 낭패를 볼 수 있다는 경고다.