The Protection Map: what a build protected

The HTML report that shows what AfterPack did to your code: every token coloured by how hard it is to reverse.

The Protection Map is how you verify your protection: your original source, with every token coloured by how obfuscated it became and what it would take to reverse.

$ npx afterpack@latest dist/

You get this map:

entitlements.jsmedium · complexity 8Open full size
entitlements.js, 54 lines, built at the medium preset with a directive around the signature check. Click any token to see what was applied to it.

How to read it

Tokens are coloured by complexity score, a measure of how obfuscated each region is, targeted by complexity. The strongest transform that touched a token sets its reversal-class label. These are static-attacker classes: they describe what someone reading the bytes faces. Four labels:

ColourLabelReversal classWhat it means
DimPreservedNoneUntouched: third-party imports, public exports, anything excluded or marked skip. Left readable so callers still resolve.
GreenRenamed & encodedpolynomial-injectiveIdentifiers renamed, strings encoded. Structure recoverable with real effort; defeats AI explainers and stock deobfuscators.
YellowFlattened & fusedpolynomial-non-injectiveControl flow flattened, identities fused. An attacker recovers an equivalent computation, not your original names and values.
RedDestroyed & fuseddestructive fusionDecomposed and fused across the bundle. A static reader recovers an equivalent program, not which original you authored. Not available; see below.

Hover a token to see the transforms applied to it, grouped by category (for example control-flow flattening), alongside the class it lands in. Try it in the live demo.

The number behind the colour is the complexity score. It flags a region that's weaker than expected, and prompts you to raise its preset or mark it preset=hard.

A colour is not a security guarantee

A protected region resists a static read: grep, AST tooling, a blind LLM. A value the running code uses is still observable to an attacker who runs it under an instrumented runtime. Keep genuinely high-value secrets server-side. The Map shows what a static reader faces, nothing more.

Mark high-value code

The Map shows stronger protection where you ask for it. Two directive forms cover almost every case. An inline marker scopes the literal or expression on the rest of its line. A block marker alone on its line opens a region that the matching /* @afterpack end */ closes; everything between is covered, whole function bodies included.

// A single literal — API keys, license secrets, sensitive constants:
const SECRET = /* @afterpack preset=extreme */ "EXPECTED_LICENSE_SECRET";

// A whole function — validators, anti-cheat, payment flow.
// The opener is alone on its line; the matching end closes the block:
/* @afterpack preset=hard */
export function checkLicense(token: string): LicenseClaims | null {
  // ...everything in here is at complexity target 25...
}
/* @afterpack end */

// A span of several statements:
/* @afterpack preset=extreme */
const claims = JSON.parse(atob(payload));
if (claims.secret !== SECRET) return null;
/* @afterpack end */

If a region you marked shows no more protection than the code around it, the directive didn't take effect. Usually a compiler stripped the comment before AfterPack saw it. AfterPack reports this. The full directive grammar is in Directives; the markers used above are preset=hard and preset=extreme.

Free vs Pro

The Free baseline already renames, encodes, flattens, and fuses within a file. It spans both the green (polynomial-injective) and yellow (polynomial-non-injective) regions on its own.

Pro adds the surgical per-region targeting that lets you paint exactly the dozen functions where being wrong is expensive, plus the two hardening transforms (selfIntegrity, comparisonHardening). Free covers the bulk of a real codebase well; Pro is what you reach for around license checks and secrets. See them side by side in Tiers.

The red class is not available. See Reversal classes.

Generate yours

$ npx afterpack@latest dist/

You get the same interactive view rendered against your own source: a static HTML file, no login or telemetry, that you can attach to a PR. The Map renders your original source, so it is written only when your build emitted a source map for the engine to chain. It is opt-in otherwise: pass protectionMap: { enabled: true } in your plugin options and the plugin turns the bundler's map on for you. Pass --protectionMap.enabled=false to skip it. Point at your build directory: Next.js writes to .next/, Nuxt to .output/, most bundlers to dist/.

Where your map lives

A local run writes the map into .afterpack/ on your machine and sends nothing anywhere. Add that directory to your .gitignore. The CLI refuses to write the map into a served path. A published Protection Map contains your full source. Never publish it.

A Pro cloud build works differently. The map is produced by the cloud engine that did the obfuscation, the only engine that honours your preset=hard, preset=extreme, and skip directives, so it is the only map that can show their effect. AfterPack keeps it, so you open any recent build's map from the dashboard instead of regenerating it.

A stored map contains your original source

A Protection Map is your code, coloured. On a Pro cloud build it is stored on AfterPack's infrastructure and readable by every member of the workspace that owns the project. If your source shouldn't be readable workspace-wide, turn it off. See below.

What is stored, exactly:

  • The map data for each build: your original source plus the per-token metrics behind the colours, in Cloudflare R2, encrypted at rest with AES-256, alongside a row of summary numbers (coverage, inflation, weak-spot counts) that the dashboard charts. Your obfuscated output is not stored.
  • Scoped to one project, the one the build's key is bound to.
  • Readable only by members of that workspace. Not public, not indexed, no shareable link.
  • Kept for one year, and for the last 500 builds per project, whichever runs out first. An hourly job deletes whatever is past either bound. The one-year window supports comparing a release against the one before it. The 500-build cap keeps a project that builds many times a day from accumulating a year of source copies; for such a project, that cap is the bound that bites first.
  • A build that could not store a map leaves a note instead, recording the reason and the sizes involved, never your code and never a file name. It is kept and deleted on the same schedule, and the dashboard shows that build as not stored rather than as one that never ran.
  • Past that window, the dashboard says so. Notes and maps age out together; your build history does not. A build older than the retention above reads as no record.

Turning it off

Storing maps is on by default, per project. A workspace admin can switch it off in the project's settings. After that, new cloud builds for that project generate and store nothing; maps already stored age out under the retention above rather than vanishing immediately. Local Protection Maps are unaffected either way: that path never touches the network.

Your dashboard charts keep working. Turning this off stops retaining your source. It does not stop recording what a build measured. Coverage, inflation, complexity, and build time contain no code, and they continue to be recorded and charted for a project with capture switched off. The weak-spot count is the one exception, because it is counted inside a stored map. The obfuscated output is unchanged.

A stored map is the only thing AfterPack keeps at rest that contains your source. Everything else about a cloud build stays in memory for the length of one request. See Privacy & data handling.

Next