/reversing-obfuscated-javascript
Reverse engineer minified, bundled, and obfuscated browser/Node JavaScript — unpacking webpack chunks, recovering source from sourcemaps, undoing obfuscator.io string-array and control-flow obfuscation with webcrack/synchrony/restringer, and locating a signing or crypto routine
$ npx -y skills add trilwu/secskills --skill reversing-obfuscated-javascript --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
- Fires itselfAuto-invocation. Claude auto-loads it when your prompt matches the work.Auto-invocation is when the right skill fires by itself at the right moment, driven by a FLOW.md router and a hook, instead of you invoking it by name. It is the difference between a skill being installed and a skill actually getting used.Read the full definition →
- You can call itInvoke it directly when you want it.
- Slash command
/reversing-obfuscated-javascript
Context preview
The summary Claude sees to decide when to auto-load this skill.
Reverse engineer minified, bundled, and obfuscated browser/Node JavaScript — unpacking webpack chunks, recovering source from sourcemaps, undoing obfuscator.io string-array and control-flow obfuscation with webcrack/synchrony/restringer, and locating a signing or crypto routine
SKILL.md
reversing-obfuscated-javascript.SKILL.mdname: reversing-obfuscated-javascript
description: Reverse engineer minified, bundled, and obfuscated browser/Node JavaScript — unpacking webpack chunks, recovering source from sourcemaps, undoing obfuscator.io string-array and control-flow obfuscation with webcrack/synchrony/restringer, and locating a signing or crypto routine in a live bundle via Chrome DevTools. Use when a page ships a huge minified bundle, when code is full of _0x hex identifiers and a rotated string array, when a .js.map is reachable, or when you must find where a request signature or token is computed.
verified: 2026-08-07
Reversing Obfuscated JavaScript
Client-side JavaScript ships the whole program to the reader — there is no stripped binary here, only code deliberately made unreadable. That changes the job: you are almost never recovering *missing* information, you are undoing a transformation. Identify the transformation first, reverse it with the tool built for it, and fall back to hand-written AST passes only for the custom layer no off-the-shelf tool knows.
When to Use
- A page or extension ships a large minified/bundled `.js` you need to read
- Code is full of `_0x1234` identifiers, a big string array, and a rotation IIFE
at the top — the obfuscator.io signature
- A `.js.map` sourcemap is reachable, or webpack left `//# sourceMappingURL`
- You must locate where a request signature, HMAC, token, or crypto key is
computed inside a running bundle
- Reproducing a client-side algorithm (an anti-bot signal, a licence check, a
paywall gate) from the shipped code
When NOT to Use
- **React Native / Hermes mobile bundles** (`index.android.bundle`,
`main.jsbundle`, Hermes magic bytes) — use `reversing-react-native-apps`.
- **A custom bytecode VM or virtualized-JS obfuscator** where control flow is
interpreted, not just flattened — that is devirtualization; use `unpacking-protected-binaries` for the VM-lifting approach.
- **Malicious JS in a compromised site or npm package** where the goal is IOCs
and behaviour, not readability — use `hunting-web-backdoors` for planted web payloads and `auditing-supply-chain` for a malicious package.
- **Testing the web app itself** (the endpoints the JS calls, XSS, auth) — use
`testing-web-applications`; come back here only to recover a client-side algorithm it needs.
- **WASM modules** loaded by the page — that is `analyzing-binaries` territory.
Identify the Layer Before Touching a Tool
Running the wrong deobfuscator produces plausible garbage. Read the first few hundred bytes and classify:
| What you see | Transformation | Reverse it with | | --- | --- | --- | | Short names, no whitespace, readable strings | Minification only | An unminifier / prettier + rename | | `webpackChunk`, a module map `{123: function(e,t,n){…}}` | Webpack/Rollup bundling | Unbundle to per-module files | | `//# sourceMappingURL=…` or a reachable `.map` | Nothing — the source is *right there* | Sourcemap recovery | | `_0x` hex names + one big string array + a rotation IIFE | obfuscator.io string-array | webcrack / synchrony / restringer | | Nested ternaries, `while(true){switch(_0x..)}` dispatcher | Control-flow flattening | AST pass to relink the switch | | `debugger` in a `setInterval`, self-defending function | Anti-debug / self-defense | Strip the guard before other passes |
Most real bundles are **layered**: webpack on the outside, obfuscator.io on a few modules, a hand-rolled string cipher on the one function that matters. Peel outermost first.
Sourcemaps: Try This First, Always
A reachable sourcemap ends the job before it starts — it contains the original, pre-transform source. Check for it every time, because a large fraction of "obfuscated" production bundles ship or leak one:
- The `//# sourceMappingURL=` comment at the bundle's end, and the sibling
`.js.map` even when the comment was stripped (try `<bundle>.map`).
- Webpack dev artifacts and source in `webpack://` paths inside the map.
- Recover files with a sourcemap consumer — `unwebpack-sourcemap` or a short
`source-map` script walks `sourcesContent` back to a directory tree.
Treat a leaked production sourcemap as a finding in its own right when you are assessing the app, not just a convenience.
Unbundling and Deobfuscation
**`webcrack` is the first tool for a bundle**, because it does three of the layers at once: unminify, unpack webpack/browserify into per-module files, and undo obfuscator.io string-array and control-flow obfuscation. Run it, then read the module tree it produces rather than the single blob.
For a bundle that is *only* obfuscator.io, a dedicated deobfuscator is often cleaner: **`synchrony`** or **`restringer`** both resolve the string array, reverse the rotation, inline the decoder calls, and flatten the trivial control flow. Compare their output — they fail on different edge cases.
When no tool fully handles the custom layer, **write an AST pass** with Babel: parse to an AST, `@babel/traverse` to find the pattern (a specific decoder call, a constant-folded expression, the flattening dispatcher), transform the nodes, and regenerate. This is the durable skill — obfuscators change, but string-array decoding, constant folding, and dead-branch elimination are the same AST operations every time. Prototype the matcher in AST Explorer against the real code before scripting it.
Two failure modes to expect:
- **A deobfuscator that runs the code to "evaluate" the string decoder** can
execute a payload. For untrusted or malicious bundles, do the string resolution statically or in an isolated sandbox — never `eval` an unknown bundle on your host to read it.
- **Self-defending / debug-protection** re-obfuscates or spins a `debugger`
loop when tampered with. Strip the guard function first (delete the node, or override `setInterval`) or every downstream pass fights it.
Finding a Routine in a Live Bundle
When static reading is slow — a signing function buried
Read more
name: reversing-obfuscated-javascript description: Reverse engineer minified, bundled, and obfuscated browser/Node JavaScript — unpacking webpack chunks, recovering source from sourcemaps, undoing obfuscator.io string-array and control-flow obfuscation with webcrack/synchrony/restringer, and locating a signing or crypto routine in a live bundle via Chrome DevTools. Use when a page ships a huge minified bundle, when code is full of _0x hex identifiers and a rotated string array, when a .js.map is reachable, or when you must find where a request signature or token is computed. verified: 2026-08-07
Reversing Obfuscated JavaScript
Client-side JavaScript ships the whole program to the reader — there is no stripped binary here, only code deliberately made unreadable. That changes the job: you are almost never recovering *missing* information, you are undoing a transformation. Identify the transformation first, reverse it with the tool built for it, and fall back to hand-written AST passes only for the custom layer no off-the-shelf tool knows.
When to Use
- A page or extension ships a large minified/bundled `.js` you need to read
- Code is full of `_0x1234` identifiers, a big string array, and a rotation IIFE
at the top — the obfuscator.io signature
- A `.js.map` sourcemap is reachable, or webpack left `//# sourceMappingURL`
- You must locate where a request signature, HMAC, token, or crypto key is
computed inside a running bundle
- Reproducing a client-side algorithm (an anti-bot signal, a licence check, a
paywall gate) from the shipped code
When NOT to Use
- **React Native / Hermes mobile bundles** (`index.android.bundle`,
`main.jsbundle`, Hermes magic bytes) — use `reversing-react-native-apps`.
- **A custom bytecode VM or virtualized-JS obfuscator** where control flow is
interpreted, not just flattened — that is devirtualization; use `unpacking-protected-binaries` for the VM-lifting approach.
- **Malicious JS in a compromised site or npm package** where the goal is IOCs
and behaviour, not readability — use `hunting-web-backdoors` for planted web payloads and `auditing-supply-chain` for a malicious package.
- **Testing the web app itself** (the endpoints the JS calls, XSS, auth) — use
`testing-web-applications`; come back here only to recover a client-side algorithm it needs.
- **WASM modules** loaded by the page — that is `analyzing-binaries` territory.
Identify the Layer Before Touching a Tool
Running the wrong deobfuscator produces plausible garbage. Read the first few hundred bytes and classify:
| What you see | Transformation | Reverse it with | | --- | --- | --- | | Short names, no whitespace, readable strings | Minification only | An unminifier / prettier + rename | | `webpackChunk`, a module map `{123: function(e,t,n){…}}` | Webpack/Rollup bundling | Unbundle to per-module files | | `//# sourceMappingURL=…` or a reachable `.map` | Nothing — the source is *right there* | Sourcemap recovery | | `_0x` hex names + one big string array + a rotation IIFE | obfuscator.io string-array | webcrack / synchrony / restringer | | Nested ternaries, `while(true){switch(_0x..)}` dispatcher | Control-flow flattening | AST pass to relink the switch | | `debugger` in a `setInterval`, self-defending function | Anti-debug / self-defense | Strip the guard before other passes |
Most real bundles are **layered**: webpack on the outside, obfuscator.io on a few modules, a hand-rolled string cipher on the one function that matters. Peel outermost first.
Sourcemaps: Try This First, Always
A reachable sourcemap ends the job before it starts — it contains the original, pre-transform source. Check for it every time, because a large fraction of "obfuscated" production bundles ship or leak one:
- The `//# sourceMappingURL=` comment at the bundle's end, and the sibling
`.js.map` even when the comment was stripped (try `<bundle>.map`).
- Webpack dev artifacts and source in `webpack://` paths inside the map.
- Recover files with a sourcemap consumer — `unwebpack-sourcemap` or a short
`source-map` script walks `sourcesContent` back to a directory tree.
Treat a leaked production sourcemap as a finding in its own right when you are assessing the app, not just a convenience.
Unbundling and Deobfuscation
**`webcrack` is the first tool for a bundle**, because it does three of the layers at once: unminify, unpack webpack/browserify into per-module files, and undo obfuscator.io string-array and control-flow obfuscation. Run it, then read the module tree it produces rather than the single blob.
For a bundle that is *only* obfuscator.io, a dedicated deobfuscator is often cleaner: **`synchrony`** or **`restringer`** both resolve the string array, reverse the rotation, inline the decoder calls, and flatten the trivial control flow. Compare their output — they fail on different edge cases.
When no tool fully handles the custom layer, **write an AST pass** with Babel: parse to an AST, `@babel/traverse` to find the pattern (a specific decoder call, a constant-folded expression, the flattening dispatcher), transform the nodes, and regenerate. This is the durable skill — obfuscators change, but string-array decoding, constant folding, and dead-branch elimination are the same AST operations every time. Prototype the matcher in AST Explorer against the real code before scripting it.
Two failure modes to expect:
- **A deobfuscator that runs the code to "evaluate" the string decoder** can
execute a payload. For untrusted or malicious bundles, do the string resolution statically or in an isolated sandbox — never `eval` an unknown bundle on your host to read it.
- **Self-defending / debug-protection** re-obfuscates or spins a `debugger`
loop when tampered with. Strip the guard function first (delete the node, or override `setInterval`) or every downstream pass fights it.
Finding a Routine in a Live Bundle
When static reading is slow — a signing function buried
**Security skills for Claude Code that encode the judgment professionals bring, not another wrapper around a scanner.** Offense, defense, and the reverse-engineering and code-audit core both sides share. Every skill is fact-checked against primary sources.

