AfterPack 威胁模型:受防御的攻击面

AfterPack 的安全评审材料:它防护的资产、每类攻击者能还原什么、秘密如何保住、当前范围止于何处,以及与它并用的控制措施。

AfterPack 防护一项资产:你的构建产出的 JavaScript 产物。它重写这个产物,让阅读、提取指纹或打补丁都要攻击者付出真正的工作量,并让针对某次构建做的工具到下一次构建就失效。

本页供安全评审阅读。它说明受防护的范围、每类攻击者能还原什么、构建期控制在哪一点失效,以及覆盖其余部分的控制措施。

受防御的攻击面

防御对象它排除掉的攻击起作用的属性
你发布的 bundle 的可读性把 bundle 粘贴进某个工具或模型,拿回你的源代码标识符被重命名,字符串被编码,控制流被扁平化,恒等式被融合,因此把 bundle 读回你的源代码需要真正的、每次构建都要重来的功夫
你的 bundle 的结构只写一次去混淆器、特征签名或补丁脚本,然后在各次发布间反复使用每次构建一个新的 seed,所以针对某次构建的模式编写的工具到下一次就会失效
你的实现的归属证明某个已发布的 bundle 是你的代码、你的库或你的算法静态阅读者必须在每次构建上重做重命名与编码的还原工作,才能把结果与你的代码对应起来;能够彻底消除这种对应关系的、破坏来源信息的类别目前不会产生
你用于比较的字符串在 bundle 中 grep 一个令牌或门控字符串字符串下限,以及针对相等性门控的非物化。在 light 下,数字仍然可读
使用中产物的完整性修补一处校验,然后发布打过补丁的构建新的 seed 让补丁位置每次发布都改变。自完整性检查只检测对它自身校验表的修改

下文的所有内容都是对这五行的限定:需要击败的是谁、每个属性能覆盖多远,以及它覆盖不到什么。

攻击者类别

是两条轴,而不是一把尺子。**静态攻击者**阅读字节:grep、AST 工具、一遍去混淆器处理,或者一个不执行代码、只解释代码的前沿模型。动态攻击者运行产物:在打过补丁的运行时下、勾住内置对象,或者针对离线回放的已录制执行轨迹。一种变换可以对阅读者很强,而对运行者很弱,把两者混为一谈是这一领域最常见的过度宣称。

下面的阶梯按攻击者能力排序。代价是相对于其他行而言的,不是一个绝对数字。

级别攻击者范围攻击者的代价结果
A0静态模式工具:grep、AST 匹配器、确定性的去混淆处理单次构建微不足道能还原仍为明文的内容。现成的去混淆器在 grep 之外没有额外收获(测量结果)。
A1盲读模型:只读一次,不执行,不用工具单次构建微不足道读到的大部分内容都被还原了:在三个小程序上,它在 light 下完整还原了全部三个,在 medium 下两个,在 extreme 下一个;没能重建出可运行程序的地方,它仍还原了大部分埋入的秘密(测量结果)。
A2能执行代码、使用工具并有预算的智能体模型单次构建低,按构建计对该次构建失守:运行代码就能看到它使用的值。基于该次构建形态做的工具,下一次要重做。
A3在大量 AfterPack 自身输出样本上训练过的模型整个输出分布,而不是单次构建一次性投入,之后每次构建几乎为零未测量。见下文。
A4插桩的动态分析:勾住内置对象、录制执行、堆快照单次构建,可以低成本重复分析师的时间,按构建计字符串层面失守:一个运行时钩子在每个预设下都还原出了 97 个中的 96 个(实测结果)。见边界。
A5特权运行时:打过补丁的浏览器、JavaScript 层之下的原生钩子单次构建前期搭建成本高,之后几乎为零失守,对任何客户端内容都是如此。

各级别在能力上是累积的,在可能性上则不是。A0 覆盖了针对已发布 JavaScript 的绝大多数真实攻击:有人运行一个免费工具,或者把你的 bundle 粘贴进聊天窗口。A5 是罕见而昂贵的终局,大多数产品永远不会遇到。

**A0 到 A2 攻击的是单次构建。**除非你固定种子,每次构建都会用新的 seed 改变形态,所以针对某次构建写的识别器或补丁,到下一次都得重写。它并不隐藏值:代码使用的任何值,运行代码的人都能读到。

**A3 攻击的是整个输出分布。**在足够大的样本上训练过的模型,会学会对逐构建差异保持不变,而轮换种子对它毫无影响。仍然有用的是瞄准:Pro 对你标记的区域使用更重的预设,并增加两种需手动开启的变换类型:transforms.selfIntegrity.enabled 和 transforms.comparisonHardening.enabled。标记真正重要的部分;参见最佳实践。

A4 和 A5 完全绕开了静态产物。边界一节涵盖了这两者。

测量结果

每类攻击者从引擎 0.2.2 的输出中还原出了什么。测量于 2026 年 10 月,对象是五个埋入了秘密的程序和真实的生产 bundle。前沿模型这一行来自 2026 年 10 月在引擎 0.2.6 上单独进行的一次测量。

攻击者lightmediumhard 和 extreme
模式工具:webcrack、restringer、synchrony、terser不比 grep 多不比 grep 多不比 grep 多
用 grep 查找 42 个埋入的数字421818
对输出做静态求值92–100% 的字符串先做一遍保持行为不变的清理,再还原 96–98% 的字符串同样清理后还原 96–98% 的字符串
挂在内置函数上的 15 行运行时钩子97 个字符串中的 96 个97 个字符串中的 96 个97 个字符串中的 96 个
前沿模型,盲读,无工具,不执行3 个程序全部完整还原3 个中 2 个完整还原;第三个泄露了 17 个秘密中的 12 个,但没有重建出可运行的程序仅 extreme:3 个中 1 个完整还原(9 个秘密全部还原);另外两个分别泄露了 17 个中的 12 个和 3 个中的 2 个秘密,都没有重建出可运行的程序

在前沿模型这一行中,Claude Opus 5.5 只拿到混淆后的文件,它拒绝处理的 5 个文件改由 Claude Opus 4.8 处理;“完整还原”指重建的代码打印出与原程序相同的测试向量,并包含每一个埋入的秘密。注意事项:语料很小,只有三个程序;每个程序和预设只尝试一次;攻击者只有 Claude。

混淆保护的是什么

AfterPack 追求两项性质。第一是代价:还原一次构建的代价应高于它所保护的价值。第二是发布节奏优势:你的更新速度快于攻击者的工具泛化到下一次构建的速度。没有任何混淆器(包括 AfterPack)能让还原变得不可能,本页的任何内容都不应被理解为这样的承诺。

默认预设会重命名标识符并编码字符串字面量。从 medium 起,AfterPack 做的不止于此:控制流被扁平化,恒等式被融合,逻辑被重写为现成工具无法还原回去的等价形式。还原这份输出的攻击者可以恢复出一个可以运行的程序,但前提是完成这些变换所要求的真正的模式识别工作,而针对某一次构建的形态编写的工具,到下一次构建就会失效。还原类把这一点拆分成静态阅读者所面对的几个类别。

逐构建的 seed 默认随机,所以同一份源码的两次构建在每一处结构上都不同。针对第 N 次构建的模式编写的工具会在第 N+1 次失效,而有人已经从第 N 次构建中读到的值依然是读到了。只有轮换构建,这一性质才有价值;最佳实践和构建与 CI 介绍了如何把重新构建接入发布流水线。

这些本身都藏不住密钥。那需要另一种机制。

保护密钥

任何行为与你的程序相同的程序都会吐出密钥,包括静态阅读者从一个受保护区域中恢复出的程序。重塑密钥周围的代码对它毫无作用。

让密钥免于被阅读字节的人获取的是非物化,在密钥如何得以保留中有介绍。提供这一能力的设置 transforms.comparisonHardening.enabled 是一项 Pro 设置,默认关闭,在每个预设级别下的效果都相同。

面对运行你代码的人,适用的是边界一节。逐构建的差异会在每次轮换时提高重新提取密钥的代价。而针对 JavaScript 引擎的通用运行时钩子,其代价不受影响,因为它并不依赖 AfterPack 的结构。AfterPack 的工作原理把同一机制与标准输出已经做到的事放在一起说明。

边界

在被插桩的运行时下运行你代码的攻击者,能拿到代码本身用到的任何值。只要运行中的程序需要它,一个坚定的动态攻击者就能观察到它。AfterPack 让这个值难以定位,并让你能以很低的代价把它轮换掉。它不会让这个值变得无法恢复。

不要把服务端的权威放到客户端

签名密钥、长期有效的凭据,以及任何攻击者可以重复使用或转卖的东西,都应该放在服务端,无论你使用哪种混淆器。

这一限制并非 AfterPack 独有。一旦攻击者控制了运行时,每一个构建期混淆器都会撞上同一堵墙。越过这堵墙后,AfterPack 仍然保留的是下一次发布的形态:针对某次构建做的工具,到下一次还得重做。

防篡改

自完整性检查 transforms.selfIntegrity.enabled 会对它自身的校验表加一道检查。它只检测对这张表的修改。对你代码中其他任何地方的修改,例如改成 return true 的补丁,都不会被检测到。

它也检测不到被动观察:记录值的钩子、堆读取或原生插桩都不会改变它检查的内容。这是一项 Pro 设置,默认关闭且全局生效,需要附着在 medium 或更高级别的代码上,因此 light 构建只在被提高到这一级别的区域内带有它。preset=hard 不会开启它,而是把一个区域提高到复杂度目标 12,并开启字符串下限。

AfterPack 不能替代什么

AfterPack 是整套控制措施中的一项,安全评审应该看清它的边缘在哪里。下面每一行说明了构建期控制措施当前的范围,以及覆盖其余部分的控制措施。范围会随产品的发布而变化;公开路线图是 AfterPack 说明未来计划的唯一场所。

控制措施AfterPack 目前的贡献覆盖其余部分的是什么
请求层防御(机器人、爬取、滥用)想从你的 bundle 中取出结构化数据的爬虫,必须先还原它。AfterPack 作用于产物,看不到请求。挑战-应答服务(reCAPTCHA、hCaptcha、Arkose),加上按 IP、会话或 API 密钥进行的服务端速率限制。
客户端完整性(作弊、自动化、打过补丁的客户端)面对一个 preset=hard、逐构建多态的客户端,作弊或自动化脚本的作者每次发布都要重新做一遍真正的分析,而不是一次性地修补一个已知偏移量。AfterPack 提高这一代价;它不做裁决。由服务端权威校验命中、状态转换和结果,客户端只用于呈现服务端已经做出的决定。
交付完整性(代理、CDN、扩展程序)AfterPack 保护它产出的字节。它控制不了这些字节的传输路径,而使用浏览器自身请求拦截 API 的扩展程序,会在任何 JavaScript 运行之前就决定加载什么。子资源完整性(integrity="sha384-…"),让浏览器拒绝与你构建时的哈希不匹配的 bundle,再加上对任何中间方可能影响的内容做服务端校验。
应用安全(bug、注入、失效的认证)混淆会拖慢通过阅读源码发现漏洞的速度。它不会消除漏洞,而任何通过运行中的程序触达漏洞的人根本不会去读你的源码。修复 bug。npx afterpack@latest audit 会扫描一个线上站点已经在泄露的内容。
关于客户端的信任决策逐构建多态性把一次性的补丁变成一笔反复发生的重新推导代价。下文的服务端权威校验。

不能替代服务端权威校验

上面每一行都有一个共同的根源:一个必须真正生效的许可证校验、支付门控、权益规则或功能开关,都是客户端对自己做出的决定,而控制这个客户端的人就能伪造它。这是客户端执行的属性,而不是某个特定混淆器的缺陷,任何构建期变换都无法弥补它。

对于任何必须在敌对客户端面前站得住的东西,比如许可、支付、权益或对局结果,请在服务端做出决定,只让客户端展示它。AfterPack 的贡献是经济上的,而且是实实在在的:它提高了攻击确实运行在客户端上的那部分内容的反复代价。

下一步

  • 攻击者能恢复什么:深入讲解静态攻击者类别,附示意图。
  • AfterPack 的工作原理:标准输出已经对你的 bundle 做了什么。
  • Protection Map:在你自己的构建中验证哪一类作用于哪一行。
  • 最佳实践:标记什么、轮换什么、把什么留在服务端。
  • 隐私与数据处理:什么会离开你的机器、存储了什么,以及存储多久。
  • 审计:扫描一个线上 URL,找出混淆无法挽回的泄露。
  • 对比:AfterPack 与你已经熟悉的工具有何不同。
  • 层级:Free 已经覆盖了哪些级别,以及 Pro 增加了什么。