Reverse Once, Run Forever: How We Killed It | TrustSig Blog
Start for FreeENDE<br>LightDark
01Reverse Once, Run Forever: How We Killed ItThe asymmetry nobody escapes<br>Contents<br>TrustSigBlocks bots, OTP fraud and credential stuffing with zero user friction. GDPR-native, EU-hosted.<br>Try free
There's a sentence every engineer in this field eventually says out loud, usually with a sigh:
"But the code is right there in their browser."
It is. That's the whole problem, and pretending otherwise is how most bot defenses quietly fail. Anything we ship to detect automation runs inside an environment the attacker owns completely: their CPU, their debugger, their clock, their unlimited patience. They can set a breakpoint anywhere. They can run our logic ten thousand times and diff the outputs. They can rip a function out and study it in a lab.
So we made peace with an uncomfortable starting assumption: the attacker has read every line of our client code. Not "might." Has. Once you actually believe that, most of the industry's playbook reveals itself as theater, and a much more interesting design space opens up.
The asymmetry nobody escapes
Server-side, you have home-field advantage. Your detection logic lives on hardware you control, behind walls you built, observable to no one. An attacker probing it works blind, through a keyhole.<br>Client-side, every advantage flips. The code has to be there, in the page, doing its work where the bot is, because that is the only place the signals exist. Mouse kinematics, rendering quirks, timing jitter, the thousand tiny tells of a real browser driven by a real human: none of that is visible from your server. To see it, you have to run code on the attacker's turf.<br>This is the asymmetry that defines the entire discipline. And it is why the classic move (obfuscate the JavaScript, ship it, hope nobody untangles the if statement that decides bot or human) is a losing trade. Someone will untangle it. Then they patch that one line to always say "human," and your defense is now actively certifying their bots.<br>We don't treat obfuscation as a lock. A lock implies that if you don't have the key, you can't get in. Client-side, there is no key we can withhold; the attacker holds everything. We treat obfuscation as a cost : how many hours of expert reverse-engineering does it take to understand one build? And, more importantly: does that understanding expire?<br>The honest reframing is this: we are not trying to make our code permanently unreadable. That is impossible, and anyone who promises it is selling you something. We are trying to make the reward for reading it approach zero. Reverse-engineering should buy the attacker a result that is already stale, or specific to a single session, or worthless without our server. Effort in, nothing durable out.
Principle 1: Be a moving target
The first thing we took away from attackers is the thing they rely on most: stability.<br>Reverse engineering is an investment. You spend a week understanding a binary because that understanding pays off next week, and the week after. The entire economics of attacking a defense assume the defense sits still long enough to be worth studying.<br>So we don't sit still.<br>Every build reshuffles its own internals. The mapping between what an operation means and how it is encoded on the wire is regenerated from scratch each release. A signature painstakingly extracted from one version doesn't fit the next one. And it goes deeper than per-build: a meaningful chunk of the sensitive machinery is re-randomized per session . The shape of the thing you are staring at in your debugger is specific to this one visitor's one visit. Learn it perfectly, and you have learned nothing transferable.<br>Static signatures that survive a rebuild<br>The internal encoding is regenerated every build. Cross-version signatures don't carry over.
Think of it like a combination lock where the numbers are not just secret. The positions of the numbers physically rearrange themselves every time someone walks up to it. Memorizing the combination is useless, because next time the "3" is not where the "3" was.
There is a subtle discipline cost to this. A defense that mutates every build can also break every build if you are not careful. Heavy obfuscation that silently corrupts the protocol is worse than no obfuscation. So a hard rule sits underneath all of it: the transformations have to be provably behavior-preserving. We pin cross-implementation test vectors, fuzz transformed logic against a plain twin across thousands of inputs, and fail the build outright on the first disagreement. The thing has to be a moving target and be byte-for-byte correct. No exceptions.
Principle 2: Don't branch, seal
This is my favorite idea in the whole system, because it inverts an instinct every programmer has.<br>The natural way to write a check is:<br>if (debuggerDetected) {<br>zero_all_memory();<br>This is also the single most patchable line of code you will ever write. An attacker who finds...