A universal toolkit for JavaScript deobfuscation
JavaScript deobfuscation tools are everywhere.
But many of them are too aggressive, rewriting code until it breaks.
Expose Kit takes a different approach.
- No brute force
- Step-by-step, verifiable transforms
- Designed to not break your code silently
Each transformation is meant to be checked and validated, so you always know when something goes wrong.
Alongside deobfuscation, Expose Kit also provides a set of practical utilities for working with obfuscated JavaScript.
Just one step:
npm i -g expose-kit
# or
bun i -g expose-kitexpose --help
expose parsable sample.js- The first argument is the input file
(--file/--inputcan also be used) - If required options are missing, Expose Kit will prompt you
- A timeout is enabled by default to avoid hangs
Use--unlimitedfor long-running execution
First, an important premise:
It is impossible to create a static deobfuscation tool that never breaks.
Reasons include:
- Unpredictable execution (
eval, dynamic code) - Bugs or edge cases in AST manipulation
Because of this, you should verify the code at every step.
After each transformation, run:
expose parsable file.jsThis ensures the syntax is still valid.
One of the most common causes of breakage is variable name confusion.
If you try to write your own deobfuscation logic (e.g. in Python), you’ll quickly realize how painful it is to track scopes correctly.
That’s why you should always start with:
expose safe-scope input.jsThis renames bindings per scope, producing code like:
Before: var x = 810;((x) => console.log(x))(114514);
After: var x = 810;((_x) => console.log(_x))(114514);With this alone:
- The code becomes far more resistant to breakage
- Writing custom deobfuscation logic becomes much easier
- You no longer need to worry about scope collisions
After safe-scope, combine common techniques like:
expand-array,expand-objectand more
After each step, run parsable and remove-unused again.
(even if it's not frequent, we might overlook something broken)
The more you do it, the clearer it becomes.
Expose Kit will also clearly indicate whether a diff exists, making inspection easy.
Repeat this process, and the original code will gradually reveal itself.
Check whether a file is syntactically valid.
parsable: const x = 810;
not parsable: const ;x; == 810;expose parsable path/to/file.jsArgs:
- Default args only
Rename bindings per scope for safer transformations.
expose safe-scope path/to/file.js --output path/to/file.safe-scope.jsExample is here.
Args:
--o, --output <file>
Output file path- No extension →
file.safe-scope.js - With extension →
file.safe-scope.<ext>
- No extension →
Pre-evaluate const numeric/string expressions. (Safe evaluate)
const a = 1 + 2 * 3; // => 7
const c = "aaaa";
const b = "a" + c; // => "aaaaa"expose pre-evaluate path/to/file.js --output path/to/file.pre-evaluate.jsArgs:
--o, --output <file>
Output file path- No extension →
file.pre-evaluate.js - With extension →
file.pre-evaluate.<ext>
- No extension →
Notes:
- Inlines zero-arg functions that return safe array literals by hoisting them to a var.
Expand array index access for primitive values.
var a = [1, 1, 4, 5, 1, 4];
// before
console.log(a[0], a[2], a[3]);
// after
console.log(1, 4, 5);Example is here.
expose expand-array path/to/file.js --target arrayName --output path/to/file.expand-array.jsArgs:
--target <name>
Target array variable name--o, --output <file>
Output file path
Notes:
- Each replacement is validated by reparsing; unsafe replacements are skipped. (This array is intended to be immutable, so caution is required)
Expand object property access for primitive values.
const obj = { a: 1, b: 2 };
// before
console.log(obj.a, obj["b"]);
// after
console.log(1, 2);Example is here.
expose expand-object path/to/file.js --target objectName --output path/to/file.expand-object.jsArgs:
--target <name>
Target object variable name--o, --output <file>
Output file path
Notes:
- Each replacement is validated by reparsing; unsafe replacements are skipped. (This object is intended to be immutable, so caution is required)
Pack consecutive object property assignments into literals.
const obj = {};
// before
obj.a = 0;
obj["b"] = 1;
// after
const obj = { a: 0, b: 1 };Example is here.
expose object-packer path/to/file.js --output path/to/file.object-packer.jsArgs:
--o, --output <file>
Output file path
Notes:
- Packs only consecutive assignments following an empty object literal.
- Stops when a property value references the object itself.
Replace safe string literal property accesses (obj["foo"]) with dot-style access (obj.foo).
const obj = { foo: 1, bar_baz: 2 };
obj["foo"] = obj['bar'];
console.log(obj["foo"], obj["bar_baz"]);Example is here.
expose key-simplify path/to/file.js --output path/to/file.key-simplify.jsArgs:
--o, --output <file>
Output file path
Notes:
- Only rewrites computed keys that are plain identifiers.
- Leaves invalid identifier keys untouched so the output stays parseable.
Replace safe update expressions with += or -=.
// before
a++;
--b;
// after
a += 1;
b -= 1;Example is here.
expose remove-updater path/to/file.js --output path/to/file.remove-updater.jsArgs:
--o, --output <file>
Output file path
Notes:
- Only replaces update expressions whose value is not used.
- Safe for expression statements and for-loop update clauses.
Inline safe alias assignments and wrapper calls.
const a = 0;
const b = a;
const c = b;
console.log(c); // => console.log(a);function a(arg) {
return b(arg);
}
function c(arg) {
return d[arg];
}
a(0); // => b(0)
c(0); // => d[0]Example is here.
expose remove-reassign path/to/file.js --output path/to/file.remove-reassign.jsArgs:
--o, --output <file>
Output file path
Notes:
- Only inlines const/immutable alias chains.
- Skips object shorthand replacements and shadowed bindings.
- Wrapper inlining is limited to single-return passthroughs.
Remove unreachable branches and simplify conditional expressions.
if (true) {
a();
} else {
b();
}
// after
a();
const x = cond ? true : false;
// after
const x = !!cond;Example is here.
expose remove-deadcode path/to/file.js --output path/to/file.remove-deadcode.jsArgs:
--o, --output <file>
Output file path
Notes:
- Only removes branches with literal/array/object tests.
- Simplifies ternaries when both branches are booleans.
Drop the self-defending wrapper, debug guards, and console hijacks generated by anti-tamper scripts.
var wrapper = (function () {
var guard = true;
return function (ctx, fn) {
var thunk = guard ? function () {
if (fn) {
fn.apply(ctx, arguments);
fn = null;
}
} : function () {};
guard = false;
return thunk;
};
})();
wrapper(this, function () {
console.log("tamper");
});
console.log("safe");Example is here.
expose remove-anti-tamper path/to/file.js --output path/to/file.remove-anti-tamper.jsArgs:
--o, --output <file>
Output file path
Notes:
- Targets wrappers that return guarded helper functions and removes their invocations.
- Cleans up debug/console helper functions before removing the wrapper.
Inline control-flow flattening loops generated by string-based state machines.
const _0x1 = "a|b|c".split("|");
let _0x2 = 0;
while (true) {
switch (_0x1[_0x2++]) {
case "a":
console.log("a");
continue;
case "b":
console.log("b");
continue;
case "c":
console.log("c");
return;
}
break;
}Example is here.
expose control-flow-packer path/to/file.js --output path/to/file.control-flow-packer.jsArgs:
--o, --output <file>
Output file path
Notes:
- Only flattens loops whose state array comes from a literal
split. - Stops when a branch returns or a state token is missing.
Inline proxy function calls into expressions.
const add = (a, b) => a + b;
const sum = add(1, 2);
// after
const sum = 1 + 2;Example is here.
expose fn-inliner path/to/file.js --output path/to/file.fn-inliner.jsArgs:
--o, --output <file>
Output file path
Notes:
- Only inlines functions that return a single expression.
- Skips return expressions with assignments, functions, blocks, or sequences.
Split sequence expressions into statements.
// before
a, b, c;
// after
a;
b;
c;Example is here.
expose sequence-split path/to/file.js --output path/to/file.sequence-split.jsArgs:
--o, --output <file>
Output file path
Notes:
- Splits safe sequence expressions into standalone statements.
- Also normalizes single-statement control flow blocks.
Remove unused variabless.
// before
var a = 0, b = 1;
console.log(a);
// after
var a = 0;
console.log(a);Example is here.
expose remove-unused path/to/file.js --output path/to/file.remove-unused.jsArgs:
--o, --output <file>
Output file path
- Missing a feature? → Create an issue
- Not sure which command to use? → Join our Discord
Built for research, not abuse.
Want stronger obfuscation? Then build something this tool can’t reverse.