- Joined
- 21 May 2026
- Messages
- 4
- Reaction score
- 6
- Points
- 3
We gave Claude Code two obfuscated JS files. No hints. Just “recover the source.” Here’s what happened.
1/12
At Cyberizm, we’re always testing where the line between “security” and “performance theater” actually sits. So we ran a small experiment: two obfuscated JavaScript files — one open-source, one commercial — handed to Claude Code with a short, four-paragraph prompt. No clues about which obfuscator produced which file. No manual reverse-engineering.
2/12
The results?
- Open-source obfuscator (javascript-obfuscator demo): clean source in ~10 minutes.
- Commercial obfuscator (Jscrambler demo): clean source in ~20 minutes.
Both starting from tiny 13-line programs that had been bloated ~200x to demonstrate “strong obfuscation.” In real production, that 200x factor is rare — which actually makes Claude’s job easier , not harder.
3/12
Let’s start with Target 1 (open-source). Input: 1,587 lines, 68 KB — originally a `calculatePrice` function. Claude returned a clean, working reconstruction. But the time wasn’t the story. The layers were.
4/12
Nine distinct defenses stacked in that one file. And Claude identified + undid all nine without us pointing to a single one. The prompt boiled down to: “Here’s a JS file. Give me the source.”
That’s it.
5/12
First attempt: Claude wrote an 883-line static reimplementation — RC4 decryption, base64, binary deserializer, recovered ~500 encrypted strings and the env fingerprint. Then it hit a custom zigzag-varint bytecode format. Instead of giving up, it pivoted: wrote a 48-line instrumentation script that splices logging hooks into the VM at runtime.
From that trace, it recovered:
- function name, params, locals
- constants `[0.15, 100, 1, "calculatePrice"]`
- three keys (blockKey=54, jumpKey=9643, seKey=4168320119)
- the 22-instruction bytecode stream
- and then disassembled opcodes (PUSH_CONST, LOAD_ARG, MUL, GT, JMP_FALSE, RETURN…)
Final output:
js
function calculatePrice(price, quantity) {
const taxRate = 0.15;
const threshold = 100;
let total = price quantity;
if (total > threshold) total = total (1 - taxRate);
return total;
}
Names and argument order not recovered (inferred from behavior), but all literal numbers exact. Behavior matches original.
6/12
The nine layers Claude unwrapped (and why each failed):
1. String array + RC4 – statically decryptable.
2. String array rotation – runs on startup; just read the result.
3. Env fingerprint – XOR with `.length` values, identical on all JS engines. Cosmetic.
4. RC4-encrypted bytecode – key from step 3 → one line.
5. Custom binary serialization – don’t reimplement, instrument it.
6. Opcode shuffling – seed + PRNG reconstructs the table.
7. Operand XOR – one known plaintext recovers blockKey/jumpKey.
8. Stack-value XOR – integers only; floats/strings/objects in plaintext.
9. Anti-debug timing + 1,500-line VM – timing checks corrupt opcodes if you step slowly. Run full-speed with instrumentation and they never trip.
Nine layers. Zero manual intervention.
7/12
Target 2 (commercial — Jscrambler’s public demo). Input: 24,620 bytes. Original: a 5-line BACKGROUND sprite-atlas object. Claude returned clean source in ~20 minutes. This time, static analysis alone — no need for runtime instrumentation.
8/12
Different machinery, same outcome:
- Control-flow flattening (switch state-machine)
- URL-encoded XOR string blob with cyclic key
- Self-replacing decoder (primes for 8 calls, then degrades to direct lookup)
- 3D index table of opaque integer tags
- No bytecode VM
Jscrambler has a public blog post claiming AI can’t reverse their obfuscation. That’s true for a chatbot that can’t execute code. It’s not true for an agent that can.
9/12
So what does this mean for obfuscation?
One year ago: a week of focused human work.
Today: one prompt.
The real question we’re sitting with at Cyberizm: Is client-side obfuscation finished as a serious defense? Or has it just shifted from “secret” to “friction” — still useful against static analyzers and casual scrapers, but not against agents?
10/12
What’s left that an agent genuinely can’t unwrap?
- Anti-tamper integrity checks
- Environment-bound execution (hardware/context)
- Runtime self-modifying code with external dependencies
- Short-lived, per-session keys that never appear in the static bundle
We’re not convinced obfuscation is dead. But “stack more layers” is clearly not a winning strategy anymore.
11/12
If you’ve thrown an agent (Claude, GPT, or other) at obfuscated code — especially in production apps, not demos — what didn’t it crack? What actually held up?
That’s the signal we’re looking for.
12/12
We’ve got a longer write-up with exact prompts, inputs, and outputs. This thread is the TL;DR. Drop a comment or DM if you want the full breakdown — or better, if you have a counterexample where the agent failed hard.
Obfuscation isn’t dead. But the bar just moved. Again.
— Cyberizm team
1/12
At Cyberizm, we’re always testing where the line between “security” and “performance theater” actually sits. So we ran a small experiment: two obfuscated JavaScript files — one open-source, one commercial — handed to Claude Code with a short, four-paragraph prompt. No clues about which obfuscator produced which file. No manual reverse-engineering.
2/12
The results?
- Open-source obfuscator (javascript-obfuscator demo): clean source in ~10 minutes.
- Commercial obfuscator (Jscrambler demo): clean source in ~20 minutes.
Both starting from tiny 13-line programs that had been bloated ~200x to demonstrate “strong obfuscation.” In real production, that 200x factor is rare — which actually makes Claude’s job easier , not harder.
3/12
Let’s start with Target 1 (open-source). Input: 1,587 lines, 68 KB — originally a `calculatePrice` function. Claude returned a clean, working reconstruction. But the time wasn’t the story. The layers were.
4/12
Nine distinct defenses stacked in that one file. And Claude identified + undid all nine without us pointing to a single one. The prompt boiled down to: “Here’s a JS file. Give me the source.”
That’s it.
5/12
First attempt: Claude wrote an 883-line static reimplementation — RC4 decryption, base64, binary deserializer, recovered ~500 encrypted strings and the env fingerprint. Then it hit a custom zigzag-varint bytecode format. Instead of giving up, it pivoted: wrote a 48-line instrumentation script that splices logging hooks into the VM at runtime.
From that trace, it recovered:
- function name, params, locals
- constants `[0.15, 100, 1, "calculatePrice"]`
- three keys (blockKey=54, jumpKey=9643, seKey=4168320119)
- the 22-instruction bytecode stream
- and then disassembled opcodes (PUSH_CONST, LOAD_ARG, MUL, GT, JMP_FALSE, RETURN…)
Final output:
js
function calculatePrice(price, quantity) {
const taxRate = 0.15;
const threshold = 100;
let total = price quantity;
if (total > threshold) total = total (1 - taxRate);
return total;
}
Names and argument order not recovered (inferred from behavior), but all literal numbers exact. Behavior matches original.
6/12
The nine layers Claude unwrapped (and why each failed):
1. String array + RC4 – statically decryptable.
2. String array rotation – runs on startup; just read the result.
3. Env fingerprint – XOR with `.length` values, identical on all JS engines. Cosmetic.
4. RC4-encrypted bytecode – key from step 3 → one line.
5. Custom binary serialization – don’t reimplement, instrument it.
6. Opcode shuffling – seed + PRNG reconstructs the table.
7. Operand XOR – one known plaintext recovers blockKey/jumpKey.
8. Stack-value XOR – integers only; floats/strings/objects in plaintext.
9. Anti-debug timing + 1,500-line VM – timing checks corrupt opcodes if you step slowly. Run full-speed with instrumentation and they never trip.
Nine layers. Zero manual intervention.
7/12
Target 2 (commercial — Jscrambler’s public demo). Input: 24,620 bytes. Original: a 5-line BACKGROUND sprite-atlas object. Claude returned clean source in ~20 minutes. This time, static analysis alone — no need for runtime instrumentation.
8/12
Different machinery, same outcome:
- Control-flow flattening (switch state-machine)
- URL-encoded XOR string blob with cyclic key
- Self-replacing decoder (primes for 8 calls, then degrades to direct lookup)
- 3D index table of opaque integer tags
- No bytecode VM
Jscrambler has a public blog post claiming AI can’t reverse their obfuscation. That’s true for a chatbot that can’t execute code. It’s not true for an agent that can.
9/12
So what does this mean for obfuscation?
One year ago: a week of focused human work.
Today: one prompt.
The real question we’re sitting with at Cyberizm: Is client-side obfuscation finished as a serious defense? Or has it just shifted from “secret” to “friction” — still useful against static analyzers and casual scrapers, but not against agents?
10/12
What’s left that an agent genuinely can’t unwrap?
- Anti-tamper integrity checks
- Environment-bound execution (hardware/context)
- Runtime self-modifying code with external dependencies
- Short-lived, per-session keys that never appear in the static bundle
We’re not convinced obfuscation is dead. But “stack more layers” is clearly not a winning strategy anymore.
11/12
If you’ve thrown an agent (Claude, GPT, or other) at obfuscated code — especially in production apps, not demos — what didn’t it crack? What actually held up?
That’s the signal we’re looking for.
12/12
We’ve got a longer write-up with exact prompts, inputs, and outputs. This thread is the TL;DR. Drop a comment or DM if you want the full breakdown — or better, if you have a counterexample where the agent failed hard.
Obfuscation isn’t dead. But the bar just moved. Again.
— Cyberizm team
