This page is available in English only.

How the AfterPack JavaScript obfuscator works

What standard (Free) output does to your code, how it resists reversal, and what an attacker can actually recover.

AfterPack is a post-build JavaScript protector. It takes the bundle your toolchain produces and rewrites it into something a human, or an AI agent, can't read, search, or modify without disproportionate effort. It wires into your existing build in one line, or runs over the output directory with no plugin at all.

No peelable obfuscation layers

Most obfuscators ship two things an attacker can exploit: a stack of layers applied one after another, and pre-baked structures a tool recognizes on sight (a string array, even an encrypted one, a custom VM interpreter, a recognizable decoder bootstrap). The reversal recipe is mechanical: identify a layer or structure by its signature, strip it, repeat until the original falls out. webcrack and restringer do exactly this, in microseconds.

AfterPack ships none of that. There is no layer to peel back and no fixed structure to fingerprint: every part of the output depends on every other part, and the whole shape changes with each build, so a tool written against one build does not carry to the next. How it compares to those tools is its own page.

AI agents changed the threat

A frontier model doesn't just summarize minified code: agents like Claude Code now deobfuscate the output of traditional obfuscators. In one public experiment, an agent stripped both javascript-obfuscator and a commercial tool (JScrambler) back to readable logic in about ten minutes (the run is written up on our blog).

Renaming, a string array, and a few layers no longer buy protection: they are the exact patterns these tools and models are best at. The threat model ranks which adversary defeats which technique. What still holds is output with no fixed structure to learn, and a new shape every build: what a tool learns from one build does not apply to the next.

What standard obfuscation already does

AfterPack runs after your bundler, on built output, and never touches your source, through a framework plugin or the post-build CLI. Quickstart is the five-minute version.

AfterPack in your build pipelineYour source.ts / .tsx / .jsBundlerNext / Vite / …AfterPackpost-build sealPolymorphic bundledifferent every releaseCDNship itrandom seed per release → a structurally different artifact every time

Free applies all of this:

  • It encodes your string literals. At any positive complexity target the string floor is on, so a string literal is rewritten into a seed-chosen runtime decoder instead of sitting in the artifact as text. This is on by default, and it is the single biggest difference from minified code. Directive prologues, typeof comparands, property names the runtime needs and export names stay readable, and a few source string values can remain inside heavily-minified third-party bundles; the build reports how many survived and the Protection Map lists them.
  • It rewrites your logic into equivalent but information-destroying forms: mixed-boolean arithmetic, opaque predicates, an integer-expression bytecode VM. An attacker can recover a working program, not your original; the algorithmic choice is gone. transforms.<kind>.enabled toggles the individual families.
  • It reshapes control and structure: control-flow flattening, dead branches, scope deepening, cross-dependency entanglement.
  • It changes everything every build, through per-build polymorphism (below).

The zero-config default is light: the string floor plus light structural inflation. Raising the preset adds more structural work; string readability is already at its floor regardless of preset. All of this runs on every tier, Free included; Tiers is where the Free and Pro boundary lives.

A new program every build

AfterPack derives its output from a per-build seed that's random by default. Two builds of the same source come out structurally different: different names, different encodings, a different shape everywhere.

This goes further than a cosmetic reshuffle. AfterPack changes the structure of the program itself, not just the text of it: control flow is flattened and reordered, values are recomputed at each use instead of stored, and parts of the bundle that were independent come out entangled with each other. What ships is a functional equivalent of what you wrote, a different program that behaves the same way, and the seed decides which equivalent you get this time.

So there is no fixed structure to fingerprint, and a deobfuscator written against one build does not carry to the next. An attacker is left reversing one specific build by hand, and that work does not transfer: the next release is a different program again. Shipping often repeats the cost every time. Builds & releases covers how to make that a habit.

For reproducible output, for cache hits or audits, pin the seed with --seed=git on the CLI, or seed: "git" in a plugin. Both derive it from your commit. See Builds & releases.

What an attacker can actually recover

Not every transform raises the cost of reversal the same way. AfterPack sorts its protection into reversal classes; knowing which one covers a region tells you what an attacker walks away with.

These are static-attacker classes: they describe what someone who reads the bytes faces, grep, AST tooling, or an LLM that explains code without running it. An attacker who runs the artifact under an instrumented runtime is a separate, stronger case, covered under Scope below and on the threat model page.

Every build reaches both available classes across the whole bundle, and the Protection Map shows which class hit which line.

The region overrides, preset=hard and preset=extreme, raise a region's complexity target and force its string floor on, concentrating that same work where you ask for it.

How secrets survive

Protecting a secret uses a different mechanism than destroying provenance. A secret (an API key, a license seed, a threshold) appears in any program that behaves the same way, so destroying which original you authored doesn't hide it.

What hides a secret from a static or blind-LLM reader is non-materialization: the value never appears in the output. transforms.comparisonHardening.enabled delivers it for a literal your code compares against, rewriting the comparison into a one-way digest check so only the digest ships. preset=extreme on a secret literal makes recovery expensive, and the literal still exists in the output.

An attacker who runs your code under an instrumented runtime can reach any secret the code itself uses. No build-time obfuscator changes that: if the running program needs the value, a determined dynamic attacker can observe it. Non-materialization defeats the static and blind-LLM reader and raises the re-extraction cost on every rotation. It does not make a client-side secret unrecoverable. High-value secrets belong server-side, where the client never holds them.

Scope

A license check in a /* @afterpack preset=hard */ region, shipping often with a varying seed, isn't worth the engineering hours to crack before the next release lands. For most products, that is the right bar.

The defense is economic. Given unbounded time, any client-side code can be reversed to some degree. The goal is to push the cost above the value, and to make that cost recur every release. When an asset is worth more than any reversal effort, keep it server-side where the client never holds it. AfterPack strengthens that architecture; it doesn't replace it. The best-practices guide covers how to spend protection where it counts.

Next

  • Reversal classes: the three-class table in depth, per-transform.
  • Threat model: the A0 to A5 adversary ladder and the static-vs-dynamic axis.
  • Protection Map: the per-token view that proves which class hit which line.
  • Best practices: what to mark, what to leave alone, and how to rotate builds.
  • Tiers: the full Free / Pro / Team / Enterprise boundary.
  • Quickstart: install the plugin and ship protected output in minutes.
  • Frameworks: the plugin for your stack, or the CLI path when there isn't one.
  • Configuration: every key named above, and which surface can set it.
  • Presets: the minify/light/medium/hard/extreme ladder.
  • Directives: the marker grammar behind preset=hard and skip.