# 审计线上网站的 JavaScript

扫描一个线上 URL，检查公开的 source map、可读代码和泄露的凭据，并给出 100 分制的分数。与公开安全扫描器同款的扫描器，直接跑在你的终端里。

Source: https://www.afterpack.dev/zh/docs/audit

`npx afterpack@latest audit <url>` 会抓取网站发布的 JavaScript，并报告读者能从中提取出什么。它与公开的[安全扫描器](https://www.afterpack.dev/zh/security-scanner)是同一个扫描器：公开的 source map、你自己代码的可读程度、泄露的凭据，以及不计分的第三方代码、动态代码注入点和检测到的技术栈。

audit 是只读的。它不会修改任何内容，不需要[账户](https://www.afterpack.dev/zh/docs/accounts)，只接触该 URL 公开提供的内容。

> **与混淆引擎相互独立的工具**
>
> `audit` 不会运行[混淆引擎](https://www.afterpack.dev/zh/docs/concepts)。它抓取网站已发布的资源，分析其中已经暴露的内容。[构建参数](https://www.afterpack.dev/zh/docs/cli#flags)对它都不适用。

## 运行

```bash
npx afterpack@latest audit https://yoursite.com
```

协议前缀是可选的：裸主机名会被规范化为 `https://`。扫描器会抓取页面并渲染它，以便包含客户端加载的代码包，然后分析找到的每一个 JavaScript 资源。

> **仅支持线上 URL**
>
> 没有本地目录模式。不支持 `npx afterpack@latest audit ./dist`。参数必须是一个 URL。请改为审计一个预览部署。

## 它检查什么

| 检查项 | 发现什么 |
| --- | --- |
| **公开的 source map** | 你最大的几个脚本旁边的 `.map` 文件（通过响应头、注释或直接请求发现），以及它是否内嵌了原始源码。含源码的 map 会把分数上限压到 10，只含映射的 map 上限为 50。公开的 `.git` 目录等同于含源码的 map。 |
| **可读代码** | 对你自己的每个脚本，衡量读者能以普通代码形式还原多少：英文字符串、有意义的名称和属性访问，基于词法单元测量。每个文件被评为原样、压缩或混淆。占 75 分，按字节加权。逐个 token 的版本参见[Protection Map](https://www.afterpack.dev/zh/docs/protection-map)。 |
| **泄露的凭据** | 私钥、云服务密钥、支付密钥、代码仓库和聊天令牌、数据库 URL 和 bearer token，打码后连同上下文显示。占 25 分。邮箱、IP 地址和可公开的浏览器密钥只列出，不计分。 |
| **第三方代码** | 从网站提供的第三方 SDK，通过主机、路径或许可证注释识别。单独列出，不计入分数。 |
| **代码卫生** | `eval()`、`Function` 构造函数、`document.write` 和原始 HTML 注入点、第三方域名以及检测到的技术栈。只列出，不计分。 |

### 暴露的 source map

source map 会把已发布的代码包链接回你原始的、可读的源码。切勿把它发布到生产环境：攻击者下载它，就能像读你写的代码一样读出来。

audit 会抓出它。使用 AfterPack 的[生产环境](https://www.afterpack.dev/zh/docs/config#build-mode)构建不会写出 `.map`，还会删除打包工具写出的 map（参见 [`sourceMap.enabled`](https://www.afterpack.dev/zh/docs/config#sourceMap-enabled) 和 [`sourceMap.emitUrl`](https://www.afterpack.dev/zh/docs/config#sourceMap-emitUrl)），但部署时从别处复制进来的 map 仍然会被发布出去。

## 解读结果

CLI 会在每个阶段完成时打印结果，`critical` 和 `high` 级别的发现会在出现时随即显示，最后以一个汇总框收尾：百分制得分、脚本数量、按严重程度分类的发现，以及检测到的技术栈。结尾会列出每一条严重发现，并附一个完整报告的网页链接。

从上到下阅读：

1. **先看 source map。** 公开的 map 会把你写的源码原样交给读者，不管构建做了什么。从生产环境中移除所有 `.map` 文件。
2. **接着看凭据。** 轮换任何泄露的凭据，然后停止发布它。
3. **最后看可读代码。** 如果你自己的文件被评为原样或压缩，那就没有实质性的保护。用 AfterPack 跑一次构建（[快速上手](https://www.afterpack.dev/zh/docs/quickstart)大约五分钟）。

默认输出的是人类可读的文本；使用 `--diagnostics.format=json` 时，stdout 上只会输出一份 JSON 文档，其中包含发现、得分和报告链接，所有人类可读的内容都会转移到 stderr；再加上 `--diagnostics.level=none` 则会同时关闭进度阶段的输出。可分享的报告始终是这次运行结束时给出的那个网页链接。

### 未评级

页面没有网站自有代码，或扫描器无法读取这些代码时，扫描不会给分。报告显示 **未评级** 而不是一个数字，并说明原因：

- **没有自有 JavaScript。** 页面没有加载任何来自网站本身的脚本，因此没有可评级的内容。请扫描一个加载应用的页面，例如产品页面。
- **被拦截。** 网站用机器人验证或错误页面回应了扫描器，而不是页面本身。
- **脚本未能读取。** 页面加载了自有脚本，但扫描器无法读取其中的大部分。请重新扫描。

被拦截的扫描，或脚本未能读取的扫描，不会在 30 分钟的重复扫描窗口内被复用，因此新的扫描会立即运行。只有少数脚本无法读取时，扫描会按已读取的脚本评级，报告会注明漏掉了多少个。

## 退出码

| 退出码 | 触发条件 |
|---|---|
| `0` | 扫描已完成。**即使发现了严重问题也是如此。** |
| `1` | 扫描失败：URL 不可达、服务端错误、触发了每日速率限制，或者流在完成前中断。 |
| `64` | 误用：没有给出 URL、给了不止一个 URL，或者 URL 格式不正确。 |

这些是 audit 自己的退出码。混淆运行使用的[构建退出码](https://www.afterpack.dev/zh/docs/diagnostics#exit-codes)是另外一套。

> **audit 不是 CI 门禁**
>
> 因为完成的扫描无论发现了什么都会以 `0` 退出，`npx afterpack@latest audit … && deploy` 不会因为发现问题而失败。没有严重程度阈值参数：如果你想据此做门禁判断，请自己从 `--diagnostics.format=json` 中读取发现结果。把 audit 当作一个由你手动阅读的发布前审查步骤来用。[会让构建失败的环节](https://www.afterpack.dev/zh/docs/builds)是混淆运行本身。

未认证的扫描每个 IP 每 24 小时限 **3 次**，同一个网站每小时最多扫描 10 次。达到限制时会打印还需等待多久，并以 `1` 退出。30 分钟内重复扫描同一个 URL 会返回现有报告，不计入次数。

## AfterPack 徽章

把徽章放在网站页脚或 README 中。当你的主机的最新扫描发现网站自身代码是 AfterPack 输出、没有公开的 source map 或凭据、且评级为 A 时，它显示 **Protected by AfterPack**；否则显示最新分数；你的主机还没有任何扫描[为网站评级](#not-graded)时显示 **not graded**。从未扫描过的主机显示 **Scan with AfterPack**。徽章链接到最新报告，并在新扫描后一小时内更新。

![AfterPack badge](https://www.afterpack.dev/badge/www.afterpack.dev.svg?style=wide)

页脚：

```html
<a href="https://www.afterpack.dev/security-scanner/site/yoursite.com?ref=badge" rel="noopener" title="AfterPack security scan">
  <img src="https://www.afterpack.dev/badge/yoursite.com.svg?style=compact&theme=dark" alt="AfterPack badge" width="200" height="32" loading="lazy">
</a>
```

README：

```markdown
[![AfterPack badge](https://www.afterpack.dev/badge/yoursite.com.svg)](https://www.afterpack.dev/security-scanner/site/yoursite.com?ref=badge)
```

样式：页脚用 `compact`（200×32）和 `wide`（236×48），README 用 `flat`（20 px）。主题：`dark` 和 `light`。[安全扫描器](https://www.afterpack.dev/zh/security-scanner)的每个结果页都有该主机的这些代码片段。

## 下一步

- [构建与 CI](https://www.afterpack.dev/zh/docs/builds)：把混淆嵌入你的发布流水线，并固定一个 seed 以获得可复现的输出。
- [最佳实践](https://www.afterpack.dev/zh/docs/best-practices)：该标记什么、该保留什么不动，以及 source map 的规则。
- [安全扫描器](https://www.afterpack.dev/zh/security-scanner)：在浏览器里做同样的检查，无需安装，附完整的可分享报告。
- [Protection Map](https://www.afterpack.dev/zh/docs/protection-map)：一旦你开始混淆，可以查看你自己构建的逐 token 视图。
- [AfterPack 不能替代什么](https://www.afterpack.dev/zh/docs/threat-model#what-afterpack-does-not-replace)：泄露的密钥需要轮换；事后混淆它没有用。
