Skip to content
Tech News
← Back to articles

ChatGPT Won't Let You Type Until Cloudflare Reads Your React State

read original get Cloudflare Workers Developer Kit → more articles
Why This Matters

This article reveals how Cloudflare's Turnstile program employs sophisticated fingerprinting techniques that go beyond traditional browser data, verifying not just browser authenticity but also the full rendering of the ChatGPT React application. This raises concerns about user privacy and the effectiveness of bot detection methods, highlighting the increasing complexity of online verification systems. For consumers and the tech industry, understanding these mechanisms is crucial as they impact privacy, security, and the development of more advanced anti-bot solutions.

Key Takeaways

Every ChatGPT message triggers a Cloudflare Turnstile program that runs silently in your browser. I decrypted 377 of these programs from network traffic and found something that goes beyond standard browser fingerprinting.

The program checks 55 properties spanning three layers: your browser (GPU, screen, fonts), the Cloudflare network (your city, your IP, your region from edge headers), and the ChatGPT React application itself ( __reactRouterContext , loaderData , clientBootstrap ). Turnstile doesn't just verify that you're running a real browser. It verifies that you're running a real browser that has fully booted a specific React application.

A bot that spoofs browser fingerprints but doesn't render the actual ChatGPT SPA will fail.

The Encryption Was Supposed to Hide This

The Turnstile bytecode arrives encrypted. The server sends a field called turnstile.dx in the prepare response: 28,000 characters of base64 that change on every request.

The outer layer is XOR'd with the p token from the prepare request. Both travel in the same HTTP exchange, so decrypting it is straightforward:

outer = json.loads(bytes( base64decode(dx)[i] ^ p_token[i % len(p_token)] for i in range(len(base64decode(dx))) )) # → 89 VM instructions

Inside those 89 instructions, there is a 19KB encrypted blob containing the actual fingerprinting program. This inner blob uses a different XOR key that is not the p token.

Initially I assumed this key was derived from performance.now() and was truly ephemeral. Then I looked at the bytecode more carefully and found the key sitting in the instructions:

[41.02, 0.3, 22.58, 12.96, 97.35]

... continue reading