安全扫描器会在你的 JavaScript bundle 中发现什么

形似密钥的模式、指回你源码的 sourceMappingURL、库指纹,以及“明文或已压缩”的标签。自动化阅读者会检查什么,以及混淆后的构建会让报告发生哪些变化。

一个扫描器、一份渗透测试报告或一份客户的安全问卷问到你发布的 JavaScript 是否可读,而你需要给出答案。

自动化扫描器实际检查什么

扫描器不会像人那样阅读你的代码。它会对能从响应中提取出的任何文本运行一组固定的检查:用形似密钥的模式去匹配你的字符串字面量;如果存在 sourceMappingURL 注释,就顺着它找回你的原始源码;识别你打包进来的各个库的版本指纹;并把文件标记为明文或已压缩。

这些都不需要理解你的逻辑。模式匹配能找到密钥,无论周围的代码是否真的会运行。一份 source map 能让一次扫描只用一个请求就读到完整的源码。阅读报告的人看到的是标签,而“明文”或“已压缩”的标签告诉他们源码就摆在那里。

混淆后的构建会让报告发生哪些变化

字面量默认会被编码,因此密钥模式检查要找的文本不在文件中。当有可读字面量残留时,strings.leaks.max 会让构建失败,并用 DIAG_SURVIVING_READABLE_LITERAL 指出它。生产构建不会写出 .map 文件,也不会写出 sourceMappingURL 注释,除非你用 sourceMap.enabled 和 sourceMap.emitUrl 明确要求,所以没有可以顺藤摸瓜找回源码的 map。

保护级别检查仍然会把文件报告为已混淆,这本来就是这项检查的用途。输出的形态对任何查看它的人都是可见的;不再可读的是你的逻辑和你的字符串。

一个能回答问卷的发布构建

一个会编码字符串并把 map 挡在输出之外的发布构建:

{
  "preset": "hard",
  "strings": {
    "encode": true,
    "leaks": { "max": 0 }
  },
  "sourceMap": { "enabled": false }
}

sourceMap.enabled 在生产构建中本来就是关闭的。在这里显式设置它,能让每一台运行这次构建的机器都给出相同的答案。

先扫描你自己的部署

在客户动手之前,先对你自己的部署运行扫描器会运行的那些检查:

$ npx afterpack@latest audit https://your-site.example.com

afterpack audit 首先查找暴露的密钥和 source map,然后是保护级别、危险模式、检测到的技术栈,以及它能识别出指纹的组件中已知的 CVE。在流水线中,加上 --diagnostics.format=json,让结果可以被解析而不必靠抓取文本,再用 --diagnostics.level 选择返回多少细节。对你部署的每一个环境都运行它,包括预发布环境。

在本地保持 protectionMap.enabled 开启。Protection Map 能把审计发现的任何问题追溯到它所来自的源码区域。

干净的扫描结果不意味着什么

审计你自己的站点,能告诉你任何阅读者已经可以从中提取出什么,而一个编码了字面量、没有发布 map 的构建,其可读文本远少于构建它所用的源码。

混淆修复不了扫描发现的问题。有漏洞的依赖、注入 bug,或者一个已经泄露出去的凭据,在构建之后仍然存在于运行中的应用里,所以请在源头修复它们,并轮换凭据。

下一步

  • 审计:命令参考,以及如何阅读结果。
  • 威胁模型:静态阅读者与运行你代码的人之间的边界。
  • 诊断:构建或审计可能发出的每一个代码。