如何利用 pnpm 的安全控制功能防御 npm 供应链攻击

原文地址: How We're Protecting Our Newsroom from npm Supply Chain Attacks

本文并非原文的翻译,是经过我自己理解后的输出,聚焦我感兴趣或有价值的内容。对具体内容感兴趣的朋友可以去原文查看完整内容。

背景

2025 年 11 月,npm 供应链出现了蠕虫病毒攻击。很多基础且下载量较大的包都被感染,造成了非常严重的影响。而西雅图时报因为使用 pnpm 提供的安全控制功能,成功躲过了这次攻击。靠的不是什么高深的手段,而是把那段时间的依赖安装和升级"挡在门外"。

当时我们公司也因为这个事情紧急成立了安全小组,对项目中的 npm 依赖进行排查。尽管我们也没有受到影响,但让我们开始重视起安全这件事。对于大公司或者稳定的系统来说,安全与合规可能更重要。

客户端控制与 3 层纵深防线

npm 作为 JavaScript 生态最大的包管理工具,尽管其提供了一定程度的安全措施,但仍然属于发布端/平台的范围。作为使用者,也应该在客户端重视起来。

此前的供应链攻击,就是相关包作者被"钓鱼邮件"窃取了 npm 的账号,然后发布了带有恶意代码的依赖包,并在安装时利用 preinstall 或者 postinstall 钩子进行攻击。如果作为使用者,完全相信 npm 而没有一点防范意识,就很容易中招。

pnpm 则提供了一些安全控制功能,利用这些功能就可以建立起 3 层纵深防线。

1. 脚本生命周期管理(Lifecycle Script Management)

这次攻击最关键的一点,就是攻击者利用了 preinstallpostinstall 在安装依赖时植入恶意脚本。

pnpm 提供了 strictDepBuilds 选项,当设置为 true 时,会默认阻止依赖包执行构建脚本(除非显式允许)。你也可以通过配置,仅允许可信来源的包执行必要的脚本。

yaml 复制代码
strictDepBuilds: true

onlyBuiltDependencies:
  - package-with-necessary-build-scripts

ignoredBuiltDependencies:
  - package-with-unnecessary-build-scripts

2. 延迟升级(Release Cooldown)

通常情况下,npm 上的包更新速度会比较快。如果没有使用 lock 文件去锁定依赖版本,那每次安装就很可能在不知道的情况下升级,就有可能引入风险。

pnpm 提供了 minimumReleaseAge 选项,相当于给了一个冷静期,比如设置为在最新版本发布后一周再允许升级。这样可以对新版本的稳定性进行一段时间的观察。对于成熟的系统来说,稳定性往往比追新更重要。同样地,也可以设置白名单,用来应对一些紧急的 CVE 修复补丁。

yaml 复制代码
minimumReleaseAge: <duration-in-minutes>

minimumReleaseAgeExclude:
  - package-with-critical-hotfix@1.2.3

3. 信任策略(Trust Policy)

这个特性也是我从这篇文章中第一次知道的。它的作用是当软件包的信任级别低于之前版本时,就会阻止安装。简单来说,如果一个包之前一直是通过 CI/CD 流程发布,这一次却突然变成了本地提交发布,那么系统就会怀疑作者凭据可能被窃取,从而阻止安装。

其原理是 npm 会有三个信任级别(从高到低):

  1. 受信发布者:通过 GitHub Actions 发布,带有 OIDC 和 npm provenance(出处证明)。
  2. 发布来源:来自 CI/CD 的签名证明。
  3. 非受信:使用用户名/密码或者 token 进行发布。
yaml 复制代码
trustPolicy: no-downgrade

trustPolicyExclude:
  - package-that-migrated-cicd@1.2.3

总结

安全从来不是单点防御,而是一套纵深体系。

pnpm 的这三项能力------脚本生命周期管理、延迟升级、信任策略------本质上是在三个关键点"把闸门关上":安装时不轻易执行脚本、升级时给新版本留观察期、发布来源不允许突然降级。

这些策略可能会带来一点"麻烦"(比如需要维护白名单),但相比供应链攻击的破坏性,这点成本往往是值得的。而且这一套"纵深防线"的理念非常值得实践,执行起来也并不复杂:先从团队的核心项目开始试运行,逐步固化成默认配置,让安全不再依赖运气。

欢迎关注公众号:此方的手帐

个人博客: konata9.cc/

每周分享周刊/小知识,和你一起进步

相关推荐
码路飞2 小时前
GPT-5.3 Instant 终于学会好好说话了,顺手对比了下同天发布的 Gemini 3.1 Flash-Lite
java·javascript
Lee川2 小时前
从回调地狱到同步之美:JavaScript异步编程的演进之路
javascript·面试
进击的尘埃2 小时前
WebSocket 长连接方案设计:从心跳保活到断线重连的生产级实践
javascript
允许部分打工人先富起来2 小时前
在node项目中执行python脚本
前端·python·node.js
摸鱼的春哥4 小时前
Agent教程15:认识LangChain(中),状态机思维
前端·javascript·后端
明月_清风4 小时前
告别遮挡:用 scroll-padding 实现优雅的锚点跳转
前端·javascript
明月_清风4 小时前
原生 JS 侧边栏缩放:从 DOM 监听到底层优化
前端·javascript
炫饭第一名18 小时前
速通Canvas指北🦮——基础入门篇
前端·javascript·程序员
进击的尘埃19 小时前
Vue3 响应式原理:从 Proxy 到依赖收集,手撸一个迷你 reactivity
javascript