AfterPack threat model: the defended surface

The security-review artifact for AfterPack: the asset it defends, what each attacker class recovers, 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 tooling built against one build breaks on the next.

This page is written to be read in a security review. It states the defended surface, what each attacker class recovers, 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 backIdentifiers are renamed, strings encoded, control flow flattened and identities fused, so reading the bundle back to your source takes real, per-build-renewed work
Your bundle's structureWriting a deobfuscator, a signature, or a patch script once and reusing it across releasesA fresh seed per build, so tools written against one build's patterns break on the next
Attribution of your implementationProving a shipped bundle is your code, your library, or your algorithmA static reader must redo the renamed & encoded recovery work on each build before it can match the result to yours; the provenance-destroying classes that would remove the match outright are not produced today
String material you compare againstGrepping a bundle for a token or a gate stringThe string floor, and for an equality gate, non-materialization. At light, numbers stay readable
Integrity of the artifact in usePatching one check and shipping the patched buildA fresh seed moves the patch point every release. Self-integrity detects edits to its own check table only

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 attackerResult
A0Static pattern tools: grep, AST matchers, deterministic deobfuscator passesone buildTrivialRecovers what stays in plaintext. Off-the-shelf deobfuscators add nothing on top of grep (measured results).
A1A blind model, single read, no execution, no toolsone buildTrivialReversed most of what it read: on three small programs, it fully reversed all three at light, two at medium and one at extreme, and where it could not rebuild one that runs, it still recovered most planted secrets (measured results).
A2An agentic model with execution, tooling, and a budgetone buildLow, per buildFalls for that build: running the code exposes the values it uses. Tooling built on that build's shape has to be rebuilt for the next.
A3A model trained on a large sample of AfterPack's own outputthe whole distribution, not one buildOne-time investment, then negligible per buildNot measured. See below.
A4Instrumented dynamic analysis: hooked built-ins, recorded execution, heap snapshotsone build, cheaply repeatableAnalyst time, per buildFalls for strings: a runtime hook recovered 96 of 97 at every preset (measured results). 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. A fresh seed per build, unless you pin one, changes the shape, so a recognizer or a patch written against one build has to be rewritten for the next. It does not hide values: anything the code uses, someone who runs it can read.

A3 attacks the whole output distribution. A model trained on a large enough sample learns to be invariant to the per-build variance, and rotating your seed costs it nothing. What still helps is aim: Pro applies a heavier preset to the regions you mark and adds two opt-in transform kinds, transforms.selfIntegrity.enabled and transforms.comparisonHardening.enabled. Mark what actually matters; see Best practices.

A4 and A5 leave the static artifact entirely. The boundary covers both.

Measured results

What each attacker recovered from engine 0.2.2 output, measured in October 2026 on five programs with planted secrets and on real production bundles. The frontier-model row is a separate October 2026 run on engine 0.2.6.

Attackerlightmediumhard and extreme
Pattern tools: webcrack, restringer, synchrony, terserNothing beyond grepNothing beyond grepNothing beyond grep
Grep for 42 planted numbers421818
Static evaluation of the output92–100% of strings96–98% of strings, after a behavior-preserving clean-up pass96–98% of strings, after the same pass
A 15-line runtime hook on a built-in96 of 97 strings96 of 97 strings96 of 97 strings
A frontier model, blind, no tools, no executionAll 3 programs fully reversed2 of 3 fully reversed; the third gave up 12 of 17 secrets without a rebuild that runsextreme only: 1 of 3 fully reversed (9 of 9 secrets); the others gave up 12 of 17 and 2 of 3 secrets without rebuilds that run

In the frontier-model row, Claude Opus 5.5 got only the obfuscated file and the 5 files it refused went to Claude Opus 4.8; "fully reversed" means the rebuilt code prints the original's test vectors and contains every planted secret. Caveats: a small corpus of three programs, one attempt per program and preset, and Claude as the only attacker.

What obfuscation protects

AfterPack targets two properties. The first is cost: reversing a build should cost more than 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 renames identifiers and encodes string literals. From medium up, AfterPack does more than that: control flow is flattened, identities are fused, and logic is rewritten into equivalent forms that stock tools cannot fold back. An attacker who reverses that output can recover a working program, but only after the real pattern-recognition work these transforms cost, and a tool written against one build's shapes breaks on the next. 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. Tools written against build N's patterns break on build N+1, while values someone already read from build N stay read. 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 one a static reader recovers from a protected region. Reshaping the code around a secret does 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 shape of the next release: tooling built against one build has to be rebuilt for the one after it.

Anti-tamper

Self-integrity, transforms.selfIntegrity.enabled, adds a check over its own check table. It detects an edit to that table only. An edit anywhere else in your code, such as a patched return true, is not detected.

It does not detect passive observation either: a hook that logs values, a heap read or native instrumentation changes nothing it checks. It is a Pro setting, off by default and global, and needs code at medium or above to attach to, so a light build carries it only inside a region raised to one. preset=hard leaves it off and instead raises a region to complexity target 12 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