AfterPack threat model: the defended surface

The security-review artifact for AfterPack: the asset it defends, the attacker classes it holds against, how a secret survives, where the current scope ends, and the controls it sits alongside.

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 defendedThe attack it takes off the tableThe property doing the work
Your shipped bundle's readabilityPasting the bundle into a tool or a model and getting your source backAlgorithms 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 structureWriting a deobfuscator, a signature, or a patch script once and reusing it across releasesA fresh seed per build, so there is no build-invariant structure to target
Attribution of your implementationProving a shipped bundle is your code, your library, or your algorithmFusion collapses several original computations onto the same output; a static reader recovers an equivalent program, not yours
String and literal material you compare againstGrepping a bundle for a token, a threshold, a gate valueThe string floor, and for an equality gate, non-materialization
Integrity of the artifact in usePatching one check and shipping the patched buildThe 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.

RungAttackerScopeCost to the attackerVerdict
A0Static pattern tools: grep, AST matchers, deterministic deobfuscator passesone buildTrivialHolds. AfterPack ships none of the fixed patterns these tools match (how it compares).
A1A blind model, single read, no execution, no toolsone buildTrivialHolds. The transform type defeats a non-executing reader, beyond per-build variance.
A2An agentic model with execution, tooling, and a budgetone buildLow, per buildHolds per build. The work doesn't carry forward to the next release.
A3A model trained on a large sample of AfterPack's own outputthe whole distribution, not one buildOne-time investment, then negligible per buildPartially holds. See below.
A4Instrumented dynamic analysis: hooked built-ins, recorded execution, heap snapshotsone build, cheaply repeatableAnalyst time, per buildPartially falls. See the boundary.
A5A privileged runtime: patched browser, native hooks below the JavaScript layerone buildHigh setup, then negligibleFalls, 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.

Keep server-side authority off the client

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.

ControlWhat AfterPack contributes todayWhat 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 clientPer-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