# Protection Map：一次构建保护了什么

展示 AfterPack 对你的代码做了什么的 HTML 报告：每个词元都按它经历了多少变换着色。

Source: https://www.afterpack.dev/zh/docs/protection-map

Protection Map 是你验证保护效果的方式：它展示你的原始源代码，每个词元都按它经历了多少变换着色。

```bash
npx afterpack@latest dist/
```

你会得到这样一张地图：

*(live Protection Map demo embed — see https://www.afterpack.dev/protection-map-demo)*

## 如何阅读

词元按复杂度分数着色：它表示每个词元经历了多少变换，也就是 [`complexity`](https://www.afterpack.dev/zh/docs/config#complexity) 所设定的目标数值。词元越红，经历的变换就越多：

| 颜色 | 复杂度分数 |
| --- | --- |
| 灰色 | 没有增加复杂度。有意保持可读的代码也显示为灰色。 |
| 绿色 | 低于 4 |
| 黄色 | 4 到 7 |
| 红色 | 7 到 10 |
| 深红色 | 高于 10 |

颜色显示的是施加了多少变换。选中一个词元，检查器会显示它所在区域经历了什么：施加的变换、这些变换覆盖了该区域的多少，以及其中仍然可读的代码为什么保持可读。旧版引擎生成的地图没有逐词元的分数，会把所有受保护的代码显示为同一种颜色。被引擎重写过的声明，例如把 `var` 改为 `let`，或把函数改为箭头函数，会列在“Also rewritten”一行中。格式化工具会撤销这些重写，因此它们不增加复杂度，也不带颜色。

作用于某个词元的最强变换决定了它的[还原类](https://www.afterpack.dev/zh/docs/reversal-classes)标签，标签显示在它的变换旁边。这些是[静态攻击者](https://www.afterpack.dev/zh/docs/threat-model)类别：它们描述的是读取字节的人所面对的情况。共有四种标签：

| 标签 | 还原类 | 含义 |
| --- | --- | --- |
| **保留** | 无 | 未被改动：[第三方导入](https://www.afterpack.dev/zh/docs/config#paths-exclude)、公开导出，以及任何被[排除](https://www.afterpack.dev/zh/docs/config#paths-exclude)或标记为 [`skip`](https://www.afterpack.dev/zh/docs/config#skip) 的内容。保持可读，以便调用方仍能正确解析。 |
| **重命名与编码** | [多项式单射](https://www.afterpack.dev/zh/docs/reversal-classes) | 标识符被重命名，[字符串被编码](https://www.afterpack.dev/zh/docs/config#strings-encode)，控制流被扁平化，恒等式被融合。花费真正的功夫可以恢复结构；能挫败 AI 代码解释工具和现成的去混淆器。 |
| **扁平化与融合** | 多项式非单射 | 逻辑被重写，使多个不同的原始版本坍缩为同一个输出，攻击者恢复出的是一个等价的计算，而不是你的计算。目前没有任何已发布的变换能达到这一类别。 |
| **破坏与融合** | 破坏性融合 | 在整个 bundle 范围内被分解并融合。静态阅读者恢复出的是一个等价程序，而无法得知你写的原始程序是哪一个。不可用；见下文。 |

悬停在某个词元上，可以看到应用于它的变换（按类别分组，例如控制流扁平化），以及它所落入的类别。[在在线演示中试试](https://www.afterpack.dev/protection-map-demo)。

颜色背后的数字就是复杂度分数。如果某个区域经历的变换比你预期的少，就应该提高它的 [`preset`](https://www.afterpack.dev/zh/docs/config#preset)，或者把它标记为 [`preset=hard`](https://www.afterpack.dev/zh/docs/config#preset)。

> **颜色不是安全保证**
>
> 受保护的区域能抵御静态读取：grep、AST 工具、不运行代码的 LLM。运行中的代码所使用的值，对于[在插桩运行时中运行它](https://www.afterpack.dev/zh/docs/threat-model#the-boundary)的攻击者来说仍然是可观察的。真正高价值的密钥应放在[服务端](https://www.afterpack.dev/zh/docs/threat-model#not-a-substitute-for-server-authoritative-validation)。这张地图只展示静态阅读者面对的情况，仅此而已。

## 标记高价值代码

你在哪里要求，地图就在哪里显示更多的变换。两种[指令形式](https://www.afterpack.dev/zh/docs/directives#writing-one)几乎覆盖所有情况。行内标记作用于它所在行剩余部分的字面量或表达式。单独占一行的块标记会开启一个区域，由对应的 `/* @afterpack end */` 关闭；两者之间的一切都被覆盖，包括完整的函数体。

```ts
// 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 12...
}
/* @afterpack end */

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

如果你标记的区域显示的变换并不比周围代码更多，说明指令没有生效。通常是编译器在 AfterPack 看到注释之前就把它剥离了。AfterPack 会[报告这种情况](https://www.afterpack.dev/zh/docs/directives#checking-what-was-applied)。完整的指令语法见[指令](https://www.afterpack.dev/zh/docs/directives#writing-one)；上面用到的标记是 [`preset=hard`](https://www.afterpack.dev/zh/docs/config#preset) 和 [`preset=extreme`](https://www.afterpack.dev/zh/docs/config#preset)。

## Free 与 Pro

Free 在默认的 `light` 预设下就会进行重命名和编码，从 `medium` 起还会在文件内部扁平化控制流并融合恒等式。所有这些都停留在多项式单射[类别](https://www.afterpack.dev/zh/docs/reversal-classes)中：静态阅读者需要付出真正的、每次构建都要重来的功夫，才能恢复你的原始结构。

[Pro](https://www.afterpack.dev/zh/docs/pro) 增加了[精准的按区域定位](https://www.afterpack.dev/zh/docs/directives#what-you-can-set)，让你可以恰好标出那十几个一旦出错代价就很高的函数，外加两项加固变换（[`selfIntegrity`](https://www.afterpack.dev/zh/docs/config#transforms-kind-enabled)、[`comparisonHardening`](https://www.afterpack.dev/zh/docs/config#transforms-kind-enabled)）。Free 能很好地覆盖真实代码库的绝大部分；Pro 则用于许可证检查和密钥周围的代码。两者的并列对比见[层级](https://www.afterpack.dev/zh/docs/tiers)。

目前没有任何已发布的变换会产生扁平化与融合类别和破坏与融合类别。参见[还原类](https://www.afterpack.dev/zh/docs/reversal-classes)。

## 生成你自己的地图

```bash
npx afterpack@latest dist/
```

你会得到同样的交互式视图，只是基于你自己的源代码渲染：一个静态 HTML 文件，无需登录，也没有[遥测](https://www.afterpack.dev/zh/docs/config#telemetry-enabled)，可以附加到 PR 上。地图渲染的是你的原始源代码，因此默认情况下，只有当你的构建输出了可供引擎链接的 source map，并且不在 CI 中（`CI=true` 或 `CI=1`）时才会写出。否则需要主动开启：在插件选项中传入 [`protectionMap: { enabled: true }`](https://www.afterpack.dev/zh/docs/config#protectionMap-enabled)，插件就会替你开启打包工具的 map。传入 [`--protectionMap.enabled=false`](https://www.afterpack.dev/zh/docs/config#protectionMap-enabled) 可以跳过它。请指向你的构建目录：[Next.js](https://www.afterpack.dev/zh/docs/frameworks/nextjs) 写入 `.next/`，[Nuxt](https://www.afterpack.dev/zh/docs/frameworks/nuxt) 写入 `.output/`，大多数打包工具写入 `dist/`。

## 地图存放在哪里

[本地（Free）构建](https://www.afterpack.dev/zh/docs/deployment-modes)会把地图写入你机器上的 [`.afterpack/protectionMap.html`](https://www.afterpack.dev/zh/docs/cli#files)，不向任何地方发送任何内容。该目录自带一个 `.gitignore`，因此 git 会忽略它。CLI 会拒绝把地图写入对外提供服务的路径。发布出去的 Protection Map 包含你的完整源代码。绝不要发布它。

[Pro 云端构建](https://www.afterpack.dev/zh/docs/deployment-modes)不会写入任何本地文件。AfterPack 会把它的地图保存在[控制台](https://www.afterpack.dev/zh/docs/dashboard/builds)中；当 [`protectionMap.enabled`](https://www.afterpack.dev/zh/docs/config#protectionMap-enabled) 开启时，构建结束时会打印指向这张地图的链接。地图由执行混淆的云端引擎生成，只有这个引擎会遵循你的 [`preset=hard`](https://www.afterpack.dev/zh/docs/config#preset)、[`preset=extreme`](https://www.afterpack.dev/zh/docs/config#preset) 和 [`skip`](https://www.afterpack.dev/zh/docs/config#skip) 指令，因此也只有这张地图能展示它们的效果。

> **保存的地图包含你的原始源代码**
>
> Protection Map 就是你的代码，只是着了色。对于 Pro 云端构建，它存储在 AfterPack 的基础设施上，**拥有该项目的[工作区](https://www.afterpack.dev/zh/docs/dashboard/workspaces)中的每一位成员**都可以查看。如果你的源代码不应该在整个工作区内可读，请关闭它。见下文。

具体存储的内容：

- **每次构建的地图数据**：你的原始源代码，加上颜色背后的逐词元指标，存放在 Cloudflare R2 中，静态存储时使用 AES-256 加密，同时附带一行供控制台绘制图表的汇总数字（覆盖率、膨胀率、薄弱点数量）。你的混淆后输出不会被存储。
- **限定在一个项目内**，即该构建的[密钥](https://www.afterpack.dev/zh/docs/dashboard/api-keys)所绑定的项目。
- **只有该工作区的成员可以查看。**不公开，不被索引，也没有可分享的链接。
- **保留一年，且每个项目只保留最近 500 次构建，以先到者为准。**一个每小时运行的任务会删除超出任一界限的内容。一年的时间窗口便于把某个版本与上一个版本进行比较。500 次构建的上限让一天构建很多次的项目不会积累起一整年的源代码副本；对于这样的项目，先触及的是这个上限。
- **无法保存地图的构建会改为留下一条说明**，记录原因和相关的体积，从不包含你的代码，也从不包含文件名。它按相同的时间表保留和删除，控制台会把这次构建显示为*未保存*，而不是显示为从未运行过。
- **超出这个时间窗口后，控制台也会如实显示。**说明和地图一起过期；你的构建历史则不会。早于上述保留期限的构建会显示为*无记录*。

### 关闭它

保存地图默认开启，按项目设置。[工作区](https://www.afterpack.dev/zh/docs/dashboard/workspaces)管理员可以在项目设置中关闭它。此后，该项目新的云端构建不会生成也不会存储任何地图；已保存的地图按上述保留期限逐步过期，而不会立即消失。无论如何，Free 构建都不受影响：它们的地图写在本地，从不接触网络。

**你的[控制台图表](https://www.afterpack.dev/zh/docs/dashboard/builds)仍然正常工作。**关闭此功能停止的是留存你的源代码，而不是停止记录一次构建测得的数据。覆盖率、膨胀率、复杂度和构建耗时不包含任何代码，对于关闭了记录的项目，它们仍会继续被记录并绘制成图表。唯一的例外是薄弱点数量，因为它是在保存的地图内部统计的。混淆后的输出保持不变。

保存的地图是 AfterPack 静态存储的内容中唯一包含你源代码的东西。关于[云端构建](https://www.afterpack.dev/zh/docs/cloud-api)的其他一切都只在一次请求的持续时间内保存在内存中。参见[隐私与数据处理](https://www.afterpack.dev/zh/docs/privacy)。

## 下一步

- [还原类](https://www.afterpack.dev/zh/docs/reversal-classes)：这些标签背后的三个类别，按变换逐一说明。
- [最佳实践](https://www.afterpack.dev/zh/docs/best-practices)：标记正确区域的模式。
- [配置](https://www.afterpack.dev/zh/docs/config)：指令语法、按文件的规则、预设。
- [AfterPack 的工作原理](https://www.afterpack.dev/zh/docs/concepts)：深入了解三个还原类。
- [威胁模型](https://www.afterpack.dev/zh/docs/threat-model)：这些标签所基于的静态与动态这一维度。
- [隐私与数据处理](https://www.afterpack.dev/zh/docs/privacy)：云端构建会存储什么、存储多久，以及谁可以查看。
- [指令](https://www.afterpack.dev/zh/docs/directives)：可以放在区域之前的每一种标记。
- [Pro](https://www.afterpack.dev/zh/docs/pro)：解锁按区域标记和地图保存的是什么。
- [在线演示](https://www.afterpack.dev/protection-map-demo)：同样的视图，基于示例源代码，无需安装任何东西。
