Right now, while you're reading this, your shipped app is defenseless.
Not "theoretically." Not "if someone really tried." Today, anyone can point an AI
agent at your bundle, walk away, and come back to a clean, plain-English teardown
of exactly how your licensing works, where your pricing logic lives, and how to
tear it out — with the code to do it.
No cracking. No debugger. No late nights in devtools. The agent reads your files,
rebuilds your entire app in its head, and hands the attacker the map.
This isn't a warning about the future. The tools are live. So I built the thing
that fights back, and it's open source. Here's how the attack works, why it's so
devastating, and the exact moves that make it fall apart.
The attacker: a reverse-engineering machine that works for free
REA hands a coding agent a full
reverse-engineering toolbox over MCP. Aim it at your shipped app.asar or bundle
and — without ever running your app — it statically reconstructs everything:
every module, import, route, IPC channel, and string. On a real ~14 MB Electron
app that's a graph of roughly ten thousand nodes. Then the agent reads a
summary of that graph and answers, in English: "here's how feature X works, and
here's the code."
Minutes. For free. At scale. On every app you've ever shipped.
And REA isn't alone
REA is the sharpest example, but it's a category, and the category is growing:
- REA — an MCP server that gives a coding agent a full reverse-engineering toolbox. Statically reconstructs an app's whole module/IPC/route graph and lets the agent explain any feature from it.
- humanify — uses LLMs to un-minify JavaScript, handing readable, meaningful names back to your mangled code. Your obfuscation's worst nightmare.
- webcrack / wakaru — unpack and unminify webpack/bundled JS back toward the original, module-by-module source.
Wire any of these into an agent and the loop runs itself: unpack → rename →
map → explain → clone. The barrier to reverse-engineering your app just dropped
from "a skilled human with a weekend" to "a prompt."
What it eats
Starve the attacker — but first, know the diet. It feeds on:
- Your module graph — the literal shape of every
importandrequire. - Your names — every identifier you shipped is a signpost.
- Your strings — routes, errors, keys, and IPC channel names.
- Your source maps — ship these and you didn't get reverse-engineered. You published your source with a bow on it.
Its fatal weakness — and your entire opening
Here's what they don't tell you: this attacker is terrifying but narrow, and
it confesses its limits out loud.
- It is 100% static. It never runs your app. Anything that only exists at runtime is invisible to it.
- It will not guess. A name assembled at runtime? It writes "unresolved" and moves on. On a real test app, 14 of ~22 IPC operations with computed names were left blank. Holes in the map.
- It buckles at scale, dropping relationships when the graph gets big.
So forget "make it unreadable." Obfuscation is a speed bump a machine rolls over.
The real play: starve the static graph, and bury your crown jewels in the exact
blind spots this attacker admits it cannot see.
Obfuscation is paint, not armor
Minify, mangle, encrypt your strings — sure. Just know it's the weakest,
first-to-fall layer in the stack. LLM-assisted deobfuscation improves every
month, and even with garbage names, your entire structure is still sitting there
for the graph builder to walk. Scrambling names doesn't hide the map.
Real armor, ranked by how long it survives contact:
- Don't ship the secret. Move it off the device, period.
- Get it off the JS graph. WebAssembly. Bytecode.
- Turn static into runtime. Computed imports, computed channels, dispatch tables — the blanks the agent can't fill.
- Make every build different. Version-diffing: dead on arrival.
- Then obfuscate and drown the rest in decoys.
The shield
REAShield is an open-source, drop-in npm
dev-dependency that turns every move above into one config and one command:
// shield.config.cjs module.exports = { input: "src/index.js", output: "dist-shielded", preset: "balanced", protect: [ { name: "licenseCheck", mode: "relocate" }, // rip the real body off-device { name: "sealedCalc", mode: "seal" }, // ship it as ciphertext { name: "pricingLogic", mode: "wasm" }, // compile it to WebAssembly { name: "featureGate", mode: "obfuscate" }, // harden it in place ], }; npx reashield build # hardened client + a server skeleton npx reashield test # measures what the attacker can STILL see What each mode does to the agent:
- Relocate — the function's real body is lifted out of your app and into a generated server handler. The client keeps one opaque remote-call stub. You cannot graph code that was never shipped. This move never stops working — it's the oldest rule in security, enforced automatically.
- Seal — the module ships as ciphertext, decrypted at runtime with a server-fetched key. Static analysis gets a wall of bytes.
- WASM — numeric hot-paths compile to a real WebAssembly module (binary, synchronous, behavior untouched). Erased from the JS graph entirely.
- Starvation (always on) — one flat module, no source maps, encrypted strings, and literal IPC/route names rewritten into runtime-computed ones: the exact names the agent surrenders on.
- Polymorphism + decoys — every build is restructured and renamed so releases can't be diffed, and the graph is flooded with inert fake "feature" clusters so the agent's summary trips over false leads first.
Don't trust it — prove it
This is the part I refuse to hand-wave. REAShield runs the actual attacker
(REA) against your own before/after build and shows you what changed.
On the bundled example — a small library with a license check, pricing logic, and
a sealed scoring function:
- Secret strings and protected identifiers left in the shipped client: 6 → 0. The real algorithm and its constants now live only on the server.
- String literals surviving verbatim into the output: 100% → 0%.
- The import graph: collapsed to a single module.
Run npx reashield test --rea against your own app for REA's own "modules
resolved / relationships resolved" counts, before vs after — and on a real
multi-thousand-node app, that drop is brutal.
Because REA ships constantly, the harness doubles as a CI gate pinned across
REA versions: a new REA that recovers more than your committed baseline fails
the build. Protection stops being a hope and becomes a test.
The honest part — the line that makes the rest credible
I'll say the thing straight, because overclaiming is how security tools die:
Nothing that runs on someone else's machine is truly unreversible. REAShield
doesn't claim to be — and neither should you.
What it actually delivers: it turns a 10-minute automated agent job into
days of specialist human grind, and kills most automated attacks at the
door. A determined human with time still gets through. Relocation and sealing need
a backend you secure, and the request/response boundary stays observable. It's
behavior-preserving, transparent (no phone-home), and strictly defensive — it
protects code you own and ship. Never a weapon against anyone else's.
One line to walk away with: the strongest defense isn't a tool, it's a
decision. Whatever must truly stay secret belongs on your server — where no
bundle, and no agent, can ever reach it.
Repo + docs: https://github.com/HMAKT99/reashield — npm i -D reashield
Do reverse engineering? Come break the relocation and decoy seams and tell me
where they're weakest. I want the teardown.