# AfterPack vs other JavaScript obfuscators

How AfterPack compares to javascript-obfuscator, js-confuser, JScrambler, JSDefender and ByteHide on build time, output size and whether the output still runs, measured on one harness.

Source: https://www.afterpack.dev/docs/comparison

Every obfuscator claims to resist reverse engineering. What matters is [what remains readable](https://www.afterpack.dev/docs/reversal-classes) after a deobfuscator or a frontier model processes the output, and what the protection costs in build time and size.

We run javascript-obfuscator, js-confuser and ByteHide on our own harness next to AfterPack and publish the results here and on the landing page; see [How we measured](#how-we-measured). JSDefender and JScrambler are not in the current run and are described from vendor documentation only. AfterPack's own standalone numbers are on [Performance](https://www.afterpack.dev/docs/performance).

## What survives a deobfuscation pass

In one public test, a coding agent stripped both `javascript-obfuscator` and a commercial tool (JScrambler) back to readable logic in about ten minutes ([full run on our blog](https://www.afterpack.dev/blog/ai-deobfuscates-javascript)). Fixed output shapes make this cheap: a string array, even an encrypted one, a custom VM interpreter, or a self-redefining decoder bootstrap. `webcrack` and `restringer` hardcode recognizers for these shapes and strip them in microseconds; a frontier model does the same with a prompt instead of a parser.

Every AfterPack build has a different shape, so tools written against one build's patterns break on the next. Someone who runs your code can still read the values it uses: on engine 0.2.2, a 15-line runtime hook recovered 96 of 97 planted strings at every preset. The [threat model](https://www.afterpack.dev/docs/threat-model#measured-results) lists what each attacker recovers.

*(animated fixed-pipeline vs per-build-shape comparison — see the web page)*

## Feature comparison

The same six vendors and seven rows as on the landing page, in the same order.

> **Where these rows come from**
>
> The first three rows are measured on our own harness — see [How we measured](#how-we-measured) below.
> AfterPack's column in the rest comes from our own documentation and shipped product. Every other
> column is what that vendor publishes, and where a vendor publishes nothing, the cell says so.

| Feature | AfterPack (0.2.6) | javascript-obfuscator (5.6.0) | js-confuser (2.1.3) | JSDefender | ByteHide (1.0.5) | JScrambler |
|---|---|---|---|---|---|---|
| [Build time](#how-we-measured) | 2.1 s at `extreme` | 4.7 s at `medium` (2.3× longer) | 8.8 s at `high` (4.2× longer) | Not run | Not run on bundles | Not run |
| Output size, gzipped | 10.5× the minified bundle at `extreme` (8.25× to 11.19×) | 11.0× | 12.1× (single unseeded draw) | Not run | Not run on bundles | Not run |
| [Strongest preset that still runs](#how-we-measured) | `extreme` (top preset, 5/5) | `medium` (`high` hangs in headless CI, 0/5) | `high` (top preset, 5/5) | Not run | Default (all switches on: 0/5) | Not run |
| [Price & free tier](https://www.afterpack.dev/docs/tiers) | Free, unlimited local — paid from $49/mo | Free, open source — $19/mo VM add-on | Free, open source | Quote only | Book a demo | Quote only |
| Framework plugins | 13 first-party plugins | Community loaders | Node API only | None found | Plugins, vendor-claimed | CLI + API |
| Runs at the edge | Yes — WASM + Workers | Not documented | Not documented | Not documented | Not documented | Not documented |
| CSP-safe output (no `eval`, no `new Function`) | Yes, by default | Not documented | Not documented | Not documented | Not documented | Not documented |

## How we measured

Machine: one Apple M2 Max, Node 24.21.0. Every measured figure below comes from one harness run on 16 October 2026 on AfterPack engine 0.2.6 and is reproducible from the same inputs. Build times drift with machine load; sizes do not.

**Build time and output size.** Five real, already-minified production bundles, run end to end, wall-clock, median per bundle; size is gzipped output over the gzipped minified input. AfterPack and js-confuser ran at their top preset (`extreme` and `high`). javascript-obfuscator's top preset, `high-obfuscation`, enables an anti-debugger that hangs under headless or CI execution, so it ran at `medium-obfuscation`, its strongest preset whose output still completes.

**Strongest preset that still runs.** Five executable programs, protected and then run in headless CI. AfterPack and js-confuser pass 5/5 at their top preset; javascript-obfuscator scores 0/5 at `high` (the anti-debugger above) and 5/5 at `medium`, which is the number the table reports.

**AfterPack's lighter presets** on the same bundles: `light`, the default, 1.36× in 0.19 s; `medium` 7.26× in 0.82 s; `hard` 8.20× in 1.12 s.

**ByteHide.** ByteHide has no preset levels, only switches, so it ran twice on the five programs: at its default settings and with every protection switch on. The defaults keep all five programs working; with every switch on, none of the five ran. It did not run on the production bundles, so it has no build time or size figure.

**JSDefender and JScrambler.** Neither is in this run, so neither has a measured figure. JScrambler's free plan only renames identifiers and minifies; its obfuscation transforms need a paid plan. Their price, plugin, edge and CSP rows come from public documentation, and JScrambler's deobfuscation notes from a public JScrambler deobfuscator (see [vs JScrambler](#vs-jscrambler)).

## vs javascript-obfuscator

The free, open-source default most teams reach for first, and the tool `webcrack` was written against. Its output is a fixed pipeline: a string array, a rotation-decoder bootstrap, and control-flow flattening with a recognizable switch shape, once a barrier and now a signature.

- The core is Rust and ships as a native binary or WebAssembly, not a JavaScript library. On our harness ([how we measured](#how-we-measured)), AfterPack at `extreme` protected a real production bundle in a median 2.1 s, against javascript-obfuscator's 4.7 s at `medium`, at a similar gzipped size: 10.5× against 11.0×.
- Its output has the same shape on every build, which is what `webcrack` keys on. AfterPack's shape changes with every build.

Both are free; [AfterPack](https://www.afterpack.dev/docs/tiers) adds unlimited builds and no credit card.

## vs js-confuser

A newer open-source alternative, also free (MIT). On our harness ([how we measured](#how-we-measured)), the same bundles came out 12.1× the minified size at its top preset (`high`), with a median build time of 8.8 s, about four times AfterPack's at `extreme`. All five executable programs still ran correctly at that preset.

- js-confuser documents no seed option, so output is not deterministic build to build: a fixed input can produce different bytes each run.
- Public write-ups document recovering js-confuser's original logic; we have not reproduced this ourselves.

## vs JScrambler

The commercial incumbent, reached behind a sales call and an enterprise contract. Its base tier ships pattern-recognizable structure.

JScrambler's documentation states that its protections are polymorphic by default, and that protecting the same input twice does not produce the same output. It documents a randomization seed for reproducing one protection exactly.

A public, open-source JScrambler deobfuscator recognises the pieces JScrambler injects by their structure: the global-object resolver, the string decoder, the control-flow state machine and the name-alias setup. It reads the keys and constants out of each file. Its samples come from unrelated sites and share the same injected prelude, so one recognizer carries across builds and customers even though names, keys and constants change.

- A published, self-serve price ($49/mo Indie) instead of a sales-gated contract.
- The [Free tier](https://www.afterpack.dev/docs/tiers) beats most starter commercial tiers on raw capability, with no account needed.
- [Enterprise](https://www.afterpack.dev/docs/enterprise) covers teams that cannot send source to a third-party API. The zero-egress option is a [free local build](https://www.afterpack.dev/docs/deployment-modes).

## vs JSDefender

PreEmptive's commercial JavaScript protector. The engine and the CLI are not on public npm: they are downloaded from an authenticated account portal, and the license is checked over the network on every run, so a build cannot run offline or after the license lapses. The license is a floating license with a limit on concurrent builds.

The pipeline is a fixed set of thirteen code transforms, each switched on or off. Two are on by default: string-literal extraction into a coded table, and renaming of declared variables and functions. The rest cover control-flow flattening, boolean and integer literal encoding, property indirection, expression sequencing, function reordering and variable grouping. Separate options add environment locks: a self-defending integrity check, devtools blocking, console cloaking, a domain lock and a date lock. The JSDefender runtime is injected into the protected artifact, which is what public deobfuscators use: three open-source tools recover the original logic by running the decoder that ships next to the protected code.

Output is not polymorphic. The pipeline is the same on every build, and a `randomSeed` option makes the output byte-stable for a given seed, so a recognizer written against one release still matches the next.

It is not in our current harness run, so this page has no measured figure for it.

A file AfterPack cannot protect [fails the build](https://www.afterpack.dev/docs/diagnostics) instead of shipping.

## vs ByteHide

A cloud protection service. Its CLI and bundler plugins send each file to ByteHide's servers and write back what comes back, so its build time includes the network round trip.

The engine behind it appears to be javascript-obfuscator: its option names and validation messages are javascript-obfuscator's own, and with the same options and seed its output matches javascript-obfuscator 4.x once ByteHide's extra number-wrapping step is removed. It has the same fixed shapes: `webcrack` recovered a planted secret from every build we made with all protection switches on, and the default string encoding XORs every string with one key byte.

On our harness ([how we measured](#how-we-measured)) its default settings kept all five programs working; with every protection switch on, none of the five ran.

- [AfterPack's free tier](https://www.afterpack.dev/docs/tiers) runs entirely on your machine, unmetered, with no account, no card and no demo call.
- Sending source to any cloud build step — ByteHide included — is the tradeoff [Enterprise](https://www.afterpack.dev/docs/enterprise) and a [free local build](https://www.afterpack.dev/docs/deployment-modes) exist to avoid.

## vs Terser

Terser is a different category of tool: it minifies, shortening names and folding constant expressions well. Minified code keeps all of the original logic, so it offers no resistance to an LLM reading the result. Run Terser, then AfterPack on its output. AfterPack does not replace a minifier: even its [`minify` preset](https://www.afterpack.dev/docs/presets#the-ladder), which applies no obfuscation, makes an already-minified bundle larger.

## What a static attacker recovers

| Tool | What a static reader recovers |
|---|---|
| Minified only | Everything. A frontier LLM explains minified code about as easily as formatted code, since only the names changed. |
| javascript-obfuscator | Effectively everything. Fixed-pipeline output means `webcrack` reverses it in seconds. |
| js-confuser | Public write-ups document recovering its original logic; we have not reproduced this ourselves. |
| JScrambler (base tier) | Real but pattern-recognizable structure. |
| JSDefender | Effectively everything. The decoder ships in the artifact, so a public tool runs it and prints the original logic back. |
| ByteHide | Effectively everything. The output has javascript-obfuscator's fixed shapes, so `webcrack` recovers it, and the default string encoding is a single-byte XOR. |
| **AfterPack Free** | An equivalent program with [the same behavior](https://www.afterpack.dev/docs/semantic-contract). Names are renamed at every preset and strings [encoded](https://www.afterpack.dev/docs/reversal-classes) from `light` up; numbers stay readable at `light`. Pattern tools get nothing beyond grep, while static evaluation still recovers most strings ([measured](https://www.afterpack.dev/docs/threat-model#measured-results)). The next build has a different shape. |
| **[AfterPack Pro](https://www.afterpack.dev/docs/pro)** ([marked regions](https://www.afterpack.dev/docs/directives#what-you-can-set)) | An equivalent program with [the same behavior](https://www.afterpack.dev/docs/semantic-contract): the same transforms as Free, with a heavier preset on the regions you mark. |

Full class definitions live on [Reversal classes](https://www.afterpack.dev/docs/reversal-classes) and [How AfterPack works](https://www.afterpack.dev/docs/concepts#what-an-attacker-can-actually-recover). Verify your own build with the [Protection Map](https://www.afterpack.dev/docs/protection-map).

## Scope of this comparison

[Given enough time, every obfuscator's output can be reversed](https://www.afterpack.dev/docs/threat-model#what-obfuscation-protects), including AfterPack's. Against a fixed-pipeline tool, a recognizer written once works on every build. Against AfterPack, tooling built on one build's shape has to be rebuilt for the next, while a runtime hook works on every build.

## Next

- [How AfterPack works](https://www.afterpack.dev/docs/concepts): reversal classes and secret survival explained in full.
- [Tiers](https://www.afterpack.dev/docs/tiers): the Free/Pro boundary and where the surgical Pro transforms apply.
- [Threat model](https://www.afterpack.dev/docs/threat-model): the adversary ladder every claim above is scoped against.
- [Audit](https://www.afterpack.dev/docs/audit): scan any live site, yours or a competitor's, for what it currently leaks.
