Hack Any Webapp

Client-Side Enforcement

Every patch documented on this site exploits the same mistake. Not a clever zero-day, not broken crypto. The app shipped a decision to the browser and then trusted the answer the browser sent back.

The patcher and the extension are downstream of one architectural choice. Strip away the regex and the injection trick, and what is left is a thesis about where decisions are allowed to live.

The one rule

Client-side enforcement of anything is worthless unless the server independently checks everything that matters. The browser is the user's machine. Code you send there is a suggestion. The user can read it, pause it, swap in a different version, and run that instead, which is exactly what the rest of this site does in two regex rules. If a decision carries value, who paid, how many tries are left, how much currency a player holds, whether an answer was right, the server has to make that decision. The client only gets to show the result.

What trusting the client looks like

The Gizmo paywall came down because a boolean lived in the bundle. A read like isSubscribedStore.get(), and a compare like 'subscribed' === ...subscription.status. Both run in code the browser hands to the user, so flipping each one to true takes a single anchored regex. The full walkthrough is in the case studies.

The lesson is not that the regex was easy to guess. The lesson is that the decision sat on the wrong machine. Minification, obfuscation, and renaming do not move it, because the code still has to run, and anything that runs can be changed. Hiding a lock is not the same as locking the door.

DecisionWhere it is enforced nowWhere it has to live
Subscription statusread from a client-side storeverified per request against the account
Free tries / heartsa counter in page memorycounted and decremented server-side
In-app currencyset by client codeowned by the server; the client only reads it
Answer correctnessgraded inside the bundlegraded by the server; the key never ships

Gizmo: fixable without a rewrite

Gizmo is salvageable, because its server already knows who the user is. The connection is authenticated. The decisions are simply being made in the wrong place, and they can be moved without tearing the product apart.

A sound version looks like this. Answer checking moves server-side: the client sends the submitted answer over a websocket, the server grades it, and the correct answer never reaches the browser to be extracted. The server owns the hearts counter and decrements it on a wrong answer, so the client renders whatever the server reports instead of tracking lives itself. Subscription status is checked on the connection, not read from a store the user controls. Then enforcement becomes a single server decision: once a non-subscriber hits zero hearts, the server stops serving the next question.

Gizmo, done server-authoritative

With that shape, patching the bundle to claim subscribed does nothing. The server never asked the client whether it was subscribed, so there is no answer to forge. The cost is real: a stateful connection, server-side grading, actual session state. That cost is the price of the check being a check.

Prodigy: structurally broken

Prodigy is the other end of the spectrum. Its architecture runs essentially the whole game on the client, including the parts a server should own outright, like setting in-app currency and writing game state. When the client is the authority for a balance, changing that balance is not an exploit. It is calling a function the app already exposes. There is nothing to bypass, because nothing is checking.

Securing Prodigy means moving each of those onto the server and treating every value the client sends as untrusted input, including the ones the code currently sets for itself. That is not a patch. It is a re-architecture, and it touches most of the codebase.

Why this is the expensive kind of bug

You do not ship a one-line fix for client-side enforcement. There is no header, no WAF rule, no obfuscation pass that closes it, because the attacker owns the runtime. Every endpoint that changes something of value has to verify the actor and the action on the server. For a small app, that is an afternoon of work. For an architecture shaped like Prodigy's, it is a project.

Securing a codebase

I take this work on. If your app enforces things in the browser that it cannot afford to lose, I move the decisions that matter onto the server and treat the client as the hostile input it is. Send me a rough sense of the codebase and I will scope it and send back a quote. Any size.

Written by Alexey Fedorov. For how the patching itself works, start with how it works.