Back

使用 npq 自动化 npm 包安全检查

使用 npq 自动化 npm 包安全检查

npq 在 npm 包安装之前进行审计,而 npm audit 只在代码已写入 node_modules 后才报告漏洞。这一顺序差异正是问题的关键所在:等到有人审计依赖树时,恶意的 postinstall 脚本早已在你的机器上执行完毕。npq 将检查环节前移——它在安装请求阶段就检查包的元数据、发布时间、脚本内容和已知 CVE,然后询问你是否继续安装。

本指南将 npq 集成到你的终端和 pre-commit 钩子中,使审计在你拉取依赖的地方自动运行,而不是等你想起来才手动执行。

npm install -g npq

核心要点

  • npm audit 针对已写入 node_modules 的包运行;npq 在任何代码接触磁盘之前就拦截安装请求。
  • npq-hero 是随 npq 一同安装的 npm 直接替代包装器;应将 npm 别名设置为 npq-hero(而非直接指向 npq),以保留所有 npm 子命令的透传行为。
  • 默认情况下,当仅存在警告时,npq 会等待 15 秒后自动继续安装——设置 NPQ_DISABLE_AUTO_CONTINUE=true 或传入 --disable-auto-continue 参数,可将倒计时替换为显式的 (y/N) 提示。这是交互式审查机制,而非无人值守的 CI 门控。
  • 在项目目录中直接运行 npq(不带 install 子命令)会审计 package.json 中的所有依赖——适合在提交添加或升级依赖的 Pull Request 之前使用。
  • npq 的 marshalls 是启发式信号,而非恶意行为的证明——昨天发布且下载量为零的包会触发 npq 的发布时间和流行度检查,但每个合法的新包最初都处于这种状态。

npq 填补的安全空白

npq 是一个预安装安全审计工具,它在 npm 获取并执行任何代码之前,对包的元数据运行一组称为”marshalls”的检查。这与 npm audit 的工作方式恰好相反——后者读取已安装的依赖树,事后才报告 CVE。2025 年的 Shai-Hulud 攻击活动正是利用了在安装过程中执行的生命周期脚本,而这恰恰是 npm audit 无法覆盖、npq 却能防御的时间窗口。该工具以开源形式托管于 lirantal/npq,由 Liran Tal 开发。

安装 npq 并执行单次审计

全局安装 npq,然后在需要预先审查的包安装时,用 npq install 替代 npm install

npm install -g npq
npq install express

npq npm 包会安装两个可执行文件:npqnpq-hero。前者用于执行显式审计;后者是下一节介绍的 npm 直接替代包装器。当你运行 npq install express 时,npq 会获取元数据、运行 marshalls 检查、输出发现的问题,并在有内容被标记时提示你是否继续,然后再将控制权交还给 npm。

被标记的包会产生类似如下的输出:

warning Package has install scripts (preinstall, postinstall)
warning Package is older than 22 days? no — published 1 day ago
? Would you like to continue installing package(s)? (Y/n)

这个提示就是控制节点。在你作出回答或 15 秒仅警告自动继续计时器到期之前,任何内容都不会被安装。

将 npm 别名设置为 npq-hero 并持久化

要让每次安装都自动进行审计,可将 npm 别名设置为 npq-hero,并将该别名写入 shell 配置文件。npq-hero 正是为此设计的包装器——它透传所有 npm 子命令(runcipublish 等),仅拦截安装操作以优先运行审计。

echo "alias npm='npq-hero'" >> ~/.zshrc   # bash 用户请改为 ~/.bashrc
source ~/.zshrc

设置别名后,npm install fastify 会先经过 npq 的 marshalls 检查,npm 才会去获取 tarball。应将 npm 别名设置为 npq-hero 而非直接指向 npq——npq 是审计二进制文件,而 npq-hero 是完整的 npm 兼容透传包装器,详见 npq README。将别名直接指向 npq 二进制文件会导致 npq 未实现的子命令无法正常工作。

npq 究竟检查什么?

npq 运行一组称为”marshalls”的安全检查,详见 npq README。这些检查可归纳为四个实用类别:

类别检查内容
供应链信号已知漏洞(通过 OSV,或在配置后通过 Snyk)、注册表签名验证、构建来源证明
恶意软件指标是否存在 preinstall/postinstall 脚本、与热门包相似的仿冒包名(typosquatting)、新引入的二进制文件/CLI
包健康状况缺少 README、LICENSE 或仓库 URL;废弃信号;下载量
维护者信号维护者数量和联系方式、过期或无效的维护者域名、包发布时间(新包检测)和版本成熟度

这种分类方式与攻击的实际呈现方式相对应:一个带有 postinstall 脚本且下载量为零的仿冒包会同时触发多项 marshalls 检查。每项检查都是一个供你权衡的信号,而非二元判定——npq 呈现数据,将决策权交给你。

禁用自动继续以强制显式审查

默认情况下,当仅存在警告时,npq 的交互式提示会等待 15 秒后自动继续安装。设置 NPQ_DISABLE_AUTO_CONTINUE=true 或传入 --disable-auto-continue 参数,可取消倒计时,强制要求在 npq 将控制权交给 npm 之前给出明确的 (y/N) 回答。该标志和环境变量均在 npq README 中有所说明。

# 两种形式均可
NPQ_DISABLE_AUTO_CONTINUE=true npq install
npq install --disable-auto-continue

这使 npq 成为一个需要主动确认的审查步骤——适合在终端操作时以及 git commit 钩子中使用,因为这些场景下都有人在场回应提示。这并非文档记录的非交互式失败模式:提示仍然需要回答,npq 也没有提供将每个被标记的包自动转换为非零退出码的标志。

将 pnpm 路由至 npq

npq 会将控制权委托给 NPQ_PKG_MGR 中指定的包管理器,因此同一审计层也可覆盖 pnpm。可按命令单次设置该变量,或将其写入 shell 别名以供日常使用。

# 通过 pnpm 进行单次审计
NPQ_PKG_MGR=pnpm npq install fastify

# 让 pnpm 始终经过 npq
alias pnpm="NPQ_PKG_MGR=pnpm npq-hero"
echo 'alias pnpm="NPQ_PKG_MGR=pnpm npq-hero"' >> ~/.zshrc

npq README 中列出的 NPQ_PKG_MGR 变量告知 npq-hero 在审计通过后应调用哪个包管理器。别名形式为 pnpm 提供了与 npm 别名相同的保障:在 marshalls 运行之前,任何安装都不会继续执行。

添加 husky pre-commit 钩子

通过在 husky pre-commit 钩子中运行 npq,可在依赖被提交之前捕获风险。这会在每次提交时审计 package.json 中声明的依赖,对涉及依赖清单变更的提交尤为有价值。

安装 husky 并初始化(使用 husky v9 语法):

npm install --save-dev husky
npx husky init

然后将钩子写入 .husky/pre-commit

# .husky/pre-commit
if git diff --cached --name-only | grep -qE 'package(-lock)?\.json'; then
  npx npq
fi

git diff --cached 检查将审计范围限定在实际修改了 package.jsonpackage-lock.json 的提交,避免无关提交承担审计开销。不带 install 子命令的裸 npx npq 命令会审计 package.json 中声明的依赖,并在发现问题时以非零状态退出。由于 Husky 会在钩子以非零状态退出时阻止提交,这使该钩子成为一个硬性的 pre-commit 门控。npx husky init 命令和裸脚本 .husky/pre-commit 格式是 husky v9 的约定;v8 使用不同的 husky add 语法,因此在复制旧版代码片段之前请确认已安装的版本。

客观局限性:启发式信号,而非确凿证据

npq 的 marshalls 是启发式信号,而非恶意行为的证明。昨天发布且下载量为零的包,按 npq 的发布时间和流行度检查标准来看是可疑的——但每个合法的新包最初都处于这种状态。npq 将噪音归纳为一组加权信号并将决策权交给你;它不能也无法判断意图。将被标记的安装视为深入了解该包的提示,而非已确认的威胁。

npq 也不能替代 npm audit 或 Socket——它与它们相互补充。这三个工具覆盖不同的时间窗口:

工具运行时机检查内容原生支持 CI
npq安装之前发布时间、安装脚本、仿冒包名、通过 OSV(或 Snyk)获取的已知 CVE否——以交互方式提示
npm audit安装之后已安装包中的 CVE是(内置)
Socket持续运行依赖图的行为分析、合并后监控是(应用 + CLI)

npq 捕获预安装信号;npm audit 捕获已在磁盘上的包中的 CVE;Socket 在合并后对依赖图进行持续的行为监控。将 npq 的安装时门控与默认使用 --ignore-scripts 相结合(并对确实需要生命周期脚本的依赖使用 LavaMoat allow-scripts 等工具),可以封堵大部分安装时攻击面。

总结

npm 别名设置为 npq-hero,并通过 husky 钩子对提交进行门控,可在你拉取依赖的每个环节——本地终端和代码提交——都运行相同的预安装审计。从全局安装和 shell 别名开始,然后在下次修改依赖清单时接入 husky 钩子。marshalls 是启发式机制,因此请认真阅读它们标记的内容——在 postinstall 载荷已经执行之后才发现问题,远不如提前看到一条警告来得好。

常见问题

项目文档中并未记录这种用法。设置 NPQ_DISABLE_AUTO_CONTINUE=true 或传入 --disable-auto-continue 参数可取消 15 秒自动继续并强制显示 (y/N) 提示,但该提示仍然需要有人回答。目前没有文档记录的标志可将每个被标记的包自动转换为非零退出码。npq 可靠的使用场景都是交互式的:通过 npq-hero 别名在终端中使用,以及通过 husky 钩子在提交时使用——这些场景下都有人在场回应提示。

npq 和 npq-hero 是一同安装的两个独立二进制文件。npq 是显式审计命令,用于替代 npm install 运行。npq-hero 是完整的 npm 兼容包装器:它透传所有 npm 子命令(如 run、ci 和 publish),仅拦截安装操作以优先运行审计。将 npm 别名直接指向 npq 二进制文件会导致 npq 未实现的子命令无法正常工作,因此 README 建议将别名指向 npq-hero。

npq 通过 NPQ_PKG_MGR 环境变量支持 pnpm。将 NPQ_PKG_MGR 设置为 pnpm,可告知 npq-hero 在审计通过后应调用哪个包管理器。你可以按命令单次设置,例如 NPQ_PKG_MGR=pnpm npq install fastify,也可以将其写入 shell 别名,例如 alias pnpm='NPQ_PKG_MGR=pnpm npq-hero',这样每次 pnpm 安装都会优先经过 npq 的 marshalls 检查。

两者都应使用,因为它们覆盖不同的时间窗口。npq 在安装之前运行,检查包的发布时间、生命周期脚本、仿冒包名以及通过 OSV(或在配置后通过 Snyk)获取的已知漏洞数据,在恶意代码接触磁盘之前将其拦截。npm audit 在安装之后运行,报告已写入 node_modules 的包中的 CVE。npq 捕获 npm audit 无法覆盖的安装时攻击面,而 npm audit 对已安装依赖的持续 CVE 报告仍然不可或缺。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay