AfterPack defends one asset: the JavaScript artifact your build emits. It rewrites that artifact so reading it, fingerprinting it, or patching it costs an attacker real work, and so that work has to be redone from scratch on your next release.
This page is written to be read in a security review. It states the defended surface, the attacker classes AfterPack holds against, the point where a build-time control stops holding, and the controls that cover the rest.
The defended surface
| What is defended | The attack it takes off the table | The property doing the work |
|---|---|---|
| Your shipped bundle's readability | Pasting the bundle into a tool or a model and getting your source back | Algorithms are rewritten into information-destroying forms, control flow is flattened, and identities are fused, so the output does not map back to one original |
| Your bundle's structure | Writing a deobfuscator, a signature, or a patch script once and reusing it across releases | A fresh seed per build, so there is no build-invariant structure to target |
| Attribution of your implementation | Proving a shipped bundle is your code, your library, or your algorithm | Fusion collapses several original computations onto the same output; a static reader recovers an equivalent program, not yours |
| String and literal material you compare against | Grepping a bundle for a token, a threshold, a gate value | The string floor, and for an equality gate, non-materialization |
| Integrity of the artifact in use | Patching one check and shipping the patched build | The anti-tamper transform: a modified artifact corrupts its own decoding rather than running |
Everything below qualifies those five rows: who has to be defeated, how far each property reaches, and what it does not reach.
Attacker classes
Two axes, not one scale. A static attacker reads the bytes: grep, AST tooling, a deobfuscator pass, or a frontier model that explains code without executing it. A dynamic attacker runs the artifact: under a patched runtime, with built-ins hooked, or against a recorded execution trace replayed offline. A transform can be strong against a reader and weak against a runner, and conflating the two is the most common overclaim in this category.
The ladder below ranks by attacker capability. Cost is relative to the other rows, not an absolute figure.
| Rung | Attacker | Scope | Cost to the attacker | Verdict |
|---|---|---|---|---|
| A0 | Static pattern tools: grep, AST matchers, deterministic deobfuscator passes | one build | Trivial | Holds. AfterPack ships none of the fixed patterns these tools match (how it compares). |
| A1 | A blind model, single read, no execution, no tools | one build | Trivial | Holds. The transform type defeats a non-executing reader, beyond per-build variance. |
| A2 | An agentic model with execution, tooling, and a budget | one build | Low, per build | Holds per build. The work doesn't carry forward to the next release. |
| A3 | A model trained on a large sample of AfterPack's own output | the whole distribution, not one build | One-time investment, then negligible per build | Partially holds. See below. |
| A4 | Instrumented dynamic analysis: hooked built-ins, recorded execution, heap snapshots | one build, cheaply repeatable | Analyst time, per build | Partially falls. See the boundary. |
| A5 | A privileged runtime: patched browser, native hooks below the JavaScript layer | one build | High setup, then negligible | Falls, for anything client-side. |
Rungs are cumulative in capability, not in likelihood. A0 covers the overwhelming majority of real attacks on shipped JavaScript: someone runs a free tool or pastes your bundle into a chat window. A5 is the rare, expensive endgame most products never face.
A0 to A2 attack one build. Per-build randomization, a fresh seed unless you pin one, defeats all three: a recognizer written against one build does not match the next, so the attacker pays the full cost again every release.
A3 attacks the whole output distribution. A model trained on a large enough sample learns to be invariant to the per-build variance that stops A0 to A2, and rotating your seed costs it nothing. What still holds is aim: Pro raises protection on the regions you mark and adds two transform kinds, transforms.selfIntegrity.enabled and transforms.comparisonHardening.enabled, that ordinary output does not carry. Mark what actually matters; see Best practices.
A4 and A5 leave the static artifact entirely. The boundary covers both.
What obfuscation protects
AfterPack targets two measurable properties. The first is economic infeasibility: the cost to reverse a build exceeds the value of what it protects. The second is a release-cadence advantage: your update rate outpaces how fast an attacker's tooling generalizes to the next build. No obfuscator, AfterPack included, makes reversal impossible, and nothing on this page should be read as claiming it does.
The default preset does more than rename things. Algorithms are rewritten into information-destroying forms, control flow is flattened, and identities are fused, so several different original computations collapse to the same output. An attacker who reverses that output gets a working program that differs from the one you wrote: the decomposition, the algorithmic choices, and any in-house pattern that would fingerprint it as your code are gone. A static reader cannot show which original you authored, and that is what protects your intellectual property. Reversal classes splits this into the classes a static reader faces.
The per-build seed is random by default, so two builds of the same source come out structurally different everywhere. A deobfuscator written against build N does not apply to build N+1. That property only pays off if you rotate builds; Best practices and Builds & CI cover wiring rebuilds into a release pipeline.
None of this hides a secret on its own. That takes a different mechanism.
Protecting a secret
A secret comes out of any program that behaves the way yours does, including the equivalent program a static reader recovers from a flattened and fused region. The transforms that destroy decomposition do nothing for it.
What protects a secret from someone reading the bytes is non-materialization, covered under How secrets survive. The setting that delivers it, transforms.comparisonHardening.enabled, is a Pro setting, off by default, and works the same at every preset level.
Against someone who runs your code, the boundary applies. Per-build variance raises the cost of re-extracting a secret on every rotation. A generic runtime hook aimed at the JavaScript engine costs the same regardless, since it does not depend on AfterPack's structure. How AfterPack works places the same mechanism next to what standard output already does.
The boundary
An attacker who runs your code under an instrumented runtime can reach any value the code itself uses. If the running program needs it, a determined dynamic attacker can observe it. AfterPack makes that value expensive to locate and cheap to rotate away from. It does not make it unrecoverable.
Signing keys, long-lived credentials, and anything an attacker could reuse or resell belong on the server, whichever obfuscator you use.
This limit is not particular to AfterPack. Every build-time obfuscator hits the same wall once an attacker controls the runtime. What AfterPack keeps past that wall is the cost of the next release: reversing one build buys an attacker nothing against the one after it.
Anti-tamper
AfterPack's anti-tamper transform does not throw an if (tampered) error an attacker can find and delete. A shimmed or mocked environment produces wrong state and garbage output further downstream, which covers behavior-altering tampering: a patched API, a mocked global, a cloned object.
It does not detect passive observation. A hook that logs and passes values through unchanged, a heap read, or native-level instrumentation below the JavaScript layer reads state without modifying it, so there is nothing to corrupt. The set is opt-in and global: transforms.selfIntegrity.enabled. preset=hard leaves it off and instead raises a region to complexity target 25 with the string floor on.
What AfterPack does not replace
AfterPack is one control in a stack, and a security review should see where its edges are. Each row below states the current scope of the build-time control and the control that covers the rest. Scope changes as the product ships; the public roadmap is the only place AfterPack states what is coming.
| Control | What AfterPack contributes today | What covers the rest |
|---|---|---|
| Request-layer defense (bots, scraping, abuse) | A scraper that wants structured data out of your bundle has to reverse it first. AfterPack acts on the artifact and does not see the request. | A challenge-response service (reCAPTCHA, hCaptcha, Arkose) plus server-side rate limiting keyed on IP, session, or API key. |
| Client integrity (cheating, automation, patched clients) | A cheat or automation author working against a preset=hard, per-build-polymorphic client redoes real analysis every release instead of patching a known offset once. AfterPack raises that cost; it does not adjudicate. | Server-authoritative validation of hits, state transitions, and outcomes, with the client used to render decisions the server already made. |
| Delivery integrity (proxies, CDNs, extensions) | AfterPack protects the bytes it produces. It does not control the path they travel, and an extension using the browser's own request-blocking API decides what to load before any JavaScript runs. | Subresource Integrity (integrity="sha384-…") so the browser refuses a bundle that does not match the hash you built, plus server-side validation of anything an intermediary could influence. |
| Application security (bugs, injection, broken auth) | Obfuscation slows down finding a flaw by reading the source. It does not remove the flaw, and anyone who reaches it through the running program never reads your source at all. | Fix the bug. npx afterpack@latest audit scans a live site for what it is already leaking. |
| Trust decisions about the client | Per-build polymorphism turns a one-time patch into a recurring re-derivation cost. | Server-authoritative validation, below. |
Not a substitute for server-authoritative validation
Every row above shares a root cause: a license check, a payment gate, an entitlement rule, or a feature flag that must actually hold is a decision a client makes about itself, and whoever controls that client can forge it. This is a property of client-side execution, not a gap in any particular obfuscator, and no build-time transform closes it.
For anything that must hold against a hostile client, such as licensing, payments, entitlements, or match outcomes, make the decision server-side and let the client only display it. AfterPack's contribution is economic, and it is real: it raises the recurring cost of attacking what does run on the client.
Next
- What an attacker can recover: the static-attacker classes in depth, with the diagram.
- How AfterPack works: what standard output already does to your bundle.
- Protection Map: verify which class hit which line in your own build.
- Best practices: what to mark, rotate, and keep server-side.
- Privacy & data handling: what leaves your machine, what is stored, and for how long.
- Audit: scan a live URL for the leaks obfuscation can't undo.
- Comparison: how AfterPack differs from the tools you already know.
- Tiers: which rungs Free already covers, and what Pro adds.