The Unlocked Door: Client-Side Security Theater
When your paywall lives in JavaScript, it is not a paywall at all.
Over the years, I've seen the same pattern repeat across dozens of services: a slick demo page, a "try 5 times free" limit, and a JavaScript bundle that contains the entire enforcement logic.
Open DevTools. Delete a cookie. Unlimited access.
This post is not about any single company. It's about a pattern I see constantly: client-side enforcement protecting server-side resources. The door is locked, but the lock is on your side, and they handed you the key.
The Architecture
Here's the pattern, generalized from what I've observed:
The "Protection"
- User visits demo page
- JavaScript bundle loads
- Bundle contains trial logic: "if counter >= 5, show upsell"
- Counter stored in a cookie
- Each generation increments the cookie
That's it. That's the entire security model.
The Reality
// The "protection" is literally:
const count = getCookie('generation_count');
if (count >= 5) {
showPaywall();
return;
}
// ... proceed with API call
setCookie('generation_count', count + 1);
The server has no idea how many times you've used the demo. It just processes requests. The limit exists entirely in JavaScript that you control.
Three Ways Through the Unlocked Door
1. Delete the Cookie
The simplest bypass. Open DevTools, Application tab, delete the counter cookie. Refresh. You have 5 more tries. Repeat forever.
2. Modify the Bundle
Use a resource override extension to serve an edited version of their JavaScript. Find the trial logic, delete it. The check no longer exists in your runtime.
They ship you the code. Your browser executes it. You decide what it means.
3. Call the API Directly
This is where it gets interesting.
In the worst cases I've seen, demo pages fetch API tokens from public endpoints. No authentication required. The token is a real bearer credential for production infrastructure.
// Pattern I've observed - public token endpoint:
const response = await fetch('https://[demo-server]/token');
const { apiToken } = await response.json();
// apiToken is now a valid bearer token for production API
With that token, you can hit the API directly, bypassing the demo UI entirely. The "5 tries" limit? It only exists in the UI you're no longer using.
What This Enables
| Attack | Difficulty | Impact |
|---|---|---|
| Unlimited personal use | Trivial | Lost conversion |
| API cost exhaustion | Easy | Financial |
| Shadow service | Moderate | Competitive |
| Capability enumeration | Easy | Intelligence |
The shadow service scenario is the scary one. Someone could:
- Fetch fresh tokens programmatically (they expire hourly, but the endpoint is public)
- Proxy requests through their own backend
- Build a "free" version of the service
- Monetize it themselves
Technically trivial. Legally actionable. Ethically questionable. But the architecture makes it possible.
Why Companies Ship This Way
Before you judge too harshly, understand the tradeoffs:
- Conversion optimization: Friction kills signups. Every auth step loses users.
- Engineering velocity: Client-side limits ship in an afternoon. Server-side enforcement takes a sprint.
- Economic rationality: The 1% who bypass don't matter if 99% convert normally.
- Assumed obscurity: Most users don't open DevTools.
These are reasonable product decisions. They're just not security decisions.
The problem is not that they chose convenience. The problem is when teams believe client-side enforcement actually protects anything. It doesn't. It guides honest users through a funnel. That's all it can do.
The Fix
If the resource is worth protecting, protect it on the server.
Short-term
- Server-side counting: Track generations per session/fingerprint on your backend
- Rate limiting: Requests per IP per minute on the actual API endpoint
- Token scoping: Demo tokens should have hard limits baked into the credential itself
Long-term
- Lightweight auth: Email-only signup with magic links—low friction, but creates accountability
- Request signing: Prevent direct endpoint access without going through your proxy
- Anomaly detection: Flag accounts generating 1000x normal volume
None of this is exotic. It's standard API security. The gap is that demo/trial experiences often get treated as "not real" when they're hitting real infrastructure.
The Broader Pattern
This is not unique to AI services. I see it everywhere:
- Paywalled articles with the full text in the page source
- Feature flags fetched client-side that unlock premium functionality
- "Server-side rendering" that actually runs in the browser
- Mobile apps with hardcoded API keys and no certificate pinning
The common thread: trusting the client. Assuming the code you ship will execute as written. Forgetting that the user's machine is hostile territory from your perspective.
The client is in the hands of the enemy. Never trust it.
This is the architectural reality of client-server systems. Your JavaScript is a polite suggestion. The user's browser is the interpreter. They decide what your bytes mean when they execute.
A Note on Responsible Research
The patterns described here are composites from observations over many years, across authorized assessments and publicly documented vulnerabilities. Nothing in this post describes active exploitation or unauthorized access.
If you recognize this pattern in your own infrastructure, the fix is straightforward. Happy to discuss.