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
Tell HN: Dont use Claude Design, lost access to my projects after unsubscribing
Claude Design 구독을 해지했더니 기존 프로젝트에 접근이 완전히 차단됐다는 사용자 경고로, AI 도구에 중요한 작업물을 의존할 때의 리스크를 잘 보여주는 사례다.
Postmortem: TanStack NPM supply-chain compromise
2026년 5월 11일 TanStack의 42개 npm 패키지가 GitHub Actions cache poisoning과 OIDC 토큰 탈취를 조합한 공격으로 악성 버전이 배포됐으며, 공격 벡터와 대응 과정을 상세히 분석한 글이다.
Can LLMs model real-world systems in TLA+?
LLM이 TLA+ 명세를 작성할 때 문법은 잘 통과하지만 실제 시스템과의 동작 일치도(conformance)는 46% 수준에 그친다는 걸 체계적으로 검증한 벤치마크 연구로, AI 기반 형식 검증의 현실적 한계를 보여준다.
Natural Language Autoencoders: Turning Claude's Thoughts into Text
Anthropic이 LLM 내부의 숫자 벡터(활성화값)를 직접 읽을 수 있는 자연어로 변환하는 NLA 기법을 공개했다. AI가 실제로 무슨 생각을 하는지 해석하는 interpretability 연구의 새로운 진전이다.
ProgramBench: Can language models rebuild programs from scratch?
LLM이 FFmpeg, SQLite, PHP 인터프리터 같은 실제 소프트웨어를 문서만 보고 처음부터 재구현할 수 있는지 측정하는 새 벤치마크로, 최고 모델도 전체 태스크의 3%만 95% 이상 통과하는 수준에 그쳤다.
The First Token Knows: Single-Decode Confidence for Hallucination Detection
LLM이 답변의 첫 토큰을 생성할 때의 확률 분포만 봐도, 10번 샘플링하는 semantic self-consistency와 맞먹는 hallucination 탐지 성능이 나온다.