AfterPack sorts its protections into classes. Each class raises the cost of reversal differently. Knowing which class covers a region of your code tells you what an attacker recovers if they read it. The Protection Map colors every token by its complexity score and names the class of the code you select.
Static attacker, defined
These are static-attacker classes. They describe what someone who reads the bytes faces: grep, AST tooling, a deobfuscator pass, or an LLM that explains code without running it. An attacker who runs the artifact under instrumentation is a separate, stronger case, covered in the threat model.
The classes
| Class | What a static attacker recovers | Tier |
|---|---|---|
| Renamed & encoded (polynomial-injective) | The structure, with effort. Names, literals, flattened control flow and fused identities all take real pattern-recognition work. That work has to be redone on every build. | Free |
| Flattened & fused (polynomial-non-injective) | An equivalent program with the same behavior. The logic is rewritten so several different original computations collapse to the same output, and the mapping back to which one you wrote is gone. No shipped transform reaches this class today. | — |
The Free tier produces the renamed & encoded class across your entire bundle (the full matrix is in Plans & tiers). Higher presets push harder within that class. The default preset is light (complexity target 5); set a stronger preset explicitly for production, for example --preset=hard. From medium up, the engine adds control-flow flattening, identity fusion, and constant destruction uniformly across the bundle, all within the renamed & encoded class.
No build reaches them, and no region is ever classed "flattened & fused" or "destroyed & fused". /* @afterpack preset=extreme */ raises a region to complexity target 25 with the string floor on, which is real, measurable protection inside the renamed & encoded class.
What recovering a class gets an attacker
What a class gets an attacker depends on what they were after.
- A secret (an API key, a license threshold, a hardcoded table): neither class hides it on its own. A secret comes out of any program that behaves the way yours does, including the one recovered from a protected region. Protecting a secret takes a different mechanism. See Protecting a secret.
- Understanding and cloning your code: a static reader can recover a working, readable equivalent, but only after the real pattern-recognition work the renamed & encoded class costs, and that work has to be redone for every build. See What AfterPack does not replace for the limits of this property.
- Locating and patching one check (disabling a paywall, flipping one
return true): neither class helps much on its own. The attacker only needs to find that one node. Finding it again costs something, and it resets on every release. Self-integrity (transforms.selfIntegrity.enabled) does not catch this: it detects edits to its own check table only. See Best practices on rotation.
Verify it on your own build
The Protection Map shows the per-token proof: your original source, colored by each token's complexity score, with the class and the transform stack behind it on hover. Open the live demo or generate your own with npx afterpack@latest.
Next
- Threat model: the adversary ladder these classes are scoped against.
- Protection Map: the per-token view for your own build.
- Best practices: what to mark and why.
- Directives: the markers that move a region between classes.
- Presets: the ladder and the complexity target each rung sets.