避免被黑客攻击的 7 个技巧(npm、pnpm 和 bun)

BBetter Stack
컴퓨터/소프트웨어AI/미래기술

Transcript

00:00:00如果你安装了 NPM 包,你就是目标。也许不是今天,也许不是这周,但攻击
00:00:05迟早会来。仅在过去几个月里,就有数百个包被攻破,
00:00:09而且这种情况并没有减缓。所以,与其每次发生这种情况时都担惊受怕,
00:00:14我实际上去锁定了自己在 NPM、PNPM 和 BUN 上的设置,结果发现大部分
00:00:18能救你的措施只需要 30 秒就能设置好。所以在这个视频中,
00:00:23我将带你了解我所做的七项更改,从一行能直接抵御
00:00:27最常见攻击的配置,到攻击者自己提交的、甚至能在包到达
00:00:30你的机器之前就将其拦截的免费工具。让我们开始吧。
00:00:39第一个也是最简单的一个,就是停止下载新包。大多数这类供应链
00:00:44攻击在数小时内就会被发现,所以如果你不在新版本发布的瞬间
00:00:48就去安装,实际上你就避开了绝大多数供应链攻击。所有主流的 Node.js
00:00:52包管理器现在都提供某种形式的发布时效限制。对于 NPM,你在 .npmrc
00:00:57文件中进行设置,单位是天。对于 PNPM,你可以在全局 pnpm-config.yaml 文件中设置,
00:01:03或者通过使用 PNPM 工作区文件按项目进行设置,这个值实际上
00:01:08是以分钟为单位设置的。顺便提一下,从 PNPM 11 开始,默认值已经设为了一天,
00:01:14所以即使你不去设置,也还是有一定的保护。对于 BUN,你可以在
00:01:17bunfig.toml 文件中设置,可以全局或按项目设置,这个值是以秒为单位的。
00:01:23让我感到惊讶的是,针对同一个设置,三个包管理器居然
00:01:27最终采用了三种不同的单位,这恰好总结了这个生态系统。一旦你设置好这些,
00:01:32如果你安装像 TanStack 的 React Start 这样的包,你会看到它不会安装该
00:01:36版本,它实际上安装的是符合我发布时效条件的最新版本,我设置的是
00:01:41一周前。现在,如果你需要绕过这个限制,比如安装的某个包出现了
00:01:45安全问题,你需要安装最新版本,你仍然可以从
00:01:49命令行执行,但也要小心大模型(LLMs),因为我确实见过有些情况下,大模型
00:01:54直接使用这个方法绕过限制,强制安装最新版本。另外要注意的是
00:01:59npx 和 bunx 不遵守此设置,它们实际上仍然安装最新版本,
00:02:03并且 Bun 中有一个待处理的 PR 来修复这个问题。既然我们在调整设置,那我们也
00:02:07把 NPM 的安装脚本也关掉吧。PNPM 和 BUN 默认就有这种行为,
00:02:12所以它们不需要额外设置。如果你不知道的话,当你安装一个包时,该包在
00:02:16安装后被允许运行它自己的代码,这最初是为了合法的
00:02:20用例设计的,比如编译原生代码或下载二进制文件,但问题是几乎每一次
00:02:26供应链攻击都利用此方法在安装后立即在你的机器上运行恶意代码。
00:02:30如果你确实发现一个包有合法理由需要这些脚本,你仍然可以显式地
00:02:34启用它。在 PNPM 中,当你安装一个带有安装后脚本的包时,它会告诉你,
00:02:39就像这里的 esbuild,然后你可以运行 pnpm approve-builds 来选择允许哪些,
00:02:44这实际上是设置了你的 PNPM 工作区 allow-builds 配置,你也可以直接使用
00:02:48allow-build 标志在安装命令上实现同样的效果。对于 BUN,正如我所说,它
00:02:52默认也会停止这些安装脚本,但值得一提的是它实际上有一个精心策划的
00:02:56包列表允许运行这些脚本,其中包括像我安装的
00:03:00esbuild 这样的包。你可以通过在 package.json 中添加 trustedDependencies 来取消此设置,
00:03:04这样只有你显式允许的包才能运行安装后脚本。
00:03:09文档上也说如果将数组设置为空,它应该会覆盖那个默认列表,
00:03:12但对我来说似乎没生效,而且我发现这也是 GitHub 上的一个已知 bug。
00:03:17目前的变通方法是在列表中放一个值,然后默认列表就会被忽略。
00:03:21在 BUN 中,你也可以运行 bun pm untrusted 查看哪些包想要运行
00:03:26脚本但尚未被信任,然后你可以运行 bun pm trust 来允许一个,或者直接将它添加到
00:03:30trustedDependencies 数组中。你还可以通过在全局 bunfig 中设置 install scripts 为
00:03:35false 来完全禁用脚本。对于 NPM,这就有点困难了。老实说,不要使用
00:03:40NPM,使用 PNPM,但如果因为某些原因非要用 NPM,你可以通过 Lavamote 的 allow-scripts NPM 包
00:03:45来实现类似的白名单行为。这样一来,它也只是 package.json 中的一个
00:03:50白名单而已。第三个技巧是禁止基于 git 的依赖。在 NPM 中,依赖项
00:03:55可以作为 git url 声明,这会绕过注册表,甚至可以自带其 .npmrc 以
00:04:01重新启用生命周期脚本。这实际上是最近针对 TanStack 的 NPM 供应链
00:04:05攻击中使用的技巧之一。你可以通过在 NPM 配置中设置 allow-git 为 none 来阻止它,
00:04:10或者选择设置为 root,这允许基于 git 的
00:04:15依赖安装,但仅限于在根 package.json 中声明的依赖,因此很可能是
00:04:19由你显式设置的。对于 PNPM,该选项是 block-exotic-sub-dependencies。当设置为 true 时,
00:04:25只有直接依赖项(即你在根 package.json 中列出的那些)可以使用外来
00:04:29源,如 git 仓库或直接的 tarball URL。这个选项在 Bun 中尚不存在,但有一个
00:04:35PR 正在讨论添加它,所以也许很快就能看到。作为对配置更改技巧的补充,
00:04:40PNPM 实际上有一个 trust-policy 设置,我们可以将其设为 no-downgrade,这意味着
00:04:45如果某个包的信任级别与其之前版本相比降低了,PNPM 将会报错。所以如果一个
00:04:50包之前是由受信任的发布者发布的,但现在只有 provenance 或
00:04:55没有任何信任凭证,安装就会失败。这应该有助于拦截那些设法
00:05:00绕过常规发布流程的攻击。第四个技巧可能是最强大的,那就是
00:05:04使用一个能够检查你正在安装的包,并在它们
00:05:08接触你的机器之前进行审计的工具。为此,我们有两个功能强大且完全免费的工具。第一个是
00:05:14MPQ。你可以通过为常规的 NPM、PNPM 或 Bun 命令创建别名来设置它,
00:05:18这样每次安装时,它实际上都会检查几件事。它会针对 Snyk 的数据库扫描已知
00:05:23漏洞,并标记任何不到 22 天大的包。它会
00:05:28捕捉拼写抢注,也就是有人发布了像 express(带两个s)这样的包,希望
00:05:32你拼写错误并安装了那个错误的包。它会验证注册表签名和
00:05:37构建来源。当包有安装前和安装后脚本时它会发出警告,而且除此之外,
00:05:41它还会检查基础项目,如下载量、是否有 README、许可证、仓库 URL、
00:05:46以及维护者的邮箱,甚至那个域名是否还在注册状态。它做完这些
00:05:51然后会给出一个关于你所安装包的交互式报告,你仍然可以决定
00:05:55是否要安装它。第二个是 Socket Firewall。这实际上是我使用的那个,
00:05:59同样,你为所有常用的包管理器加上这个别名,这个工具还支持
00:06:03JavaScript 之外的其他语言,比如 Python 和 Rust。类似于 MPQ,当你用
00:06:08这个工具执行安装时,它会检查 Socket 并阻止任何已确认的恶意包,还会警告
00:06:12你 AI 检测到的威胁,即那些尚未经过人工审核的威胁。老实说,
00:06:16我主要选择 Socket Firewall 是因为它是我第一个听说的,但我信任 Socket,因为他们
00:06:21拦截了许多最近的恶意包,攻击者甚至在一次采访中说
00:06:25Socket 会在包到达你的机器之前检测出恶意软件,这对他们来说是个相当好的
00:06:30广告,而且我也喜欢它支持 JavaScript 以外的语言,通过 uv、pip 和 cargo。
00:06:35如果你安装了其中任何一个,一定要确保你清除包管理器的
00:06:38缓存,以确保每个安装的包都经过防火墙的检查。第五个技巧关于你的
00:06:42锁文件。你的锁文件存储了每个包的实际下载 URL,而问题是,
00:06:47几乎没人会在 PR 中审查锁文件。所以如果有人对你的
00:06:51仓库提交 PR,他们可以悄悄编辑一个解析后的 URL 指向他们控制的包,并设置
00:06:56匹配的完整性哈希值,这样看起来什么都没问题。现在下一次你运行安装时,你就是从
00:07:01攻击者的服务器拉取代码。不过好消息是,如果你在使用 PNPM,你是不受此影响的。
00:07:05它不会保留那些可以被替换的 tarball 源,而且它也不会安装
00:07:09锁文件中存在但未在 package.json 中显式声明的任何内容。我找不到
00:07:14关于 Bun 的确凿信息,所以它可能仍然容易受到这种攻击。如果你不是
00:07:18使用 PNPM,修复方法是使用一个叫 lockfile-lint 的工具。你将其作为开发依赖安装,它会校验
00:07:23你的锁文件,检查每个包是否从受信任的主机(如 NPM 注册表)解析而来。它检查
00:07:28解析出的 URL 是否与包名匹配,还会检查完整性哈希是否真实
00:07:32有效。如果它怀疑有什么被篡改了,它会使安装失败。第六个技巧是停止
00:07:37在 CI 和生产环境中使用 npm install,改用 clean install。NPM 的这个命令是
00:07:42npm ci,而对于 Bun 和 PNPM,你实际上需要添加 frozen-lockfile 标志,但它们也有 CI
00:07:47命令作为别名来执行同样的操作。PNPM 如果检测到
00:07:52它在 CI 环境中运行,实际上默认就会使用它。Clean install 命令安装锁文件中完全一致的内容,
00:07:57别无他物,如果锁文件和 package.json 不匹配,它会直接停止并抛出错误,
00:08:01而不是猜测并直接安装。所以这应该能防止攻击者混入
00:08:05被篡改的版本。如果你一开始就没有提交锁文件,这些方法就都没用了,
00:08:09所以一定要确保将其纳入版本控制,而不是添加到 gitignore 中。我承认当我
00:08:13小时候刚开始学编程时,我确实认为应该忽略这些文件,所以我很庆幸
00:08:17后来学会了提交它们。上述六个技巧主要是关于配置和工具的,但还有一些
00:08:21你可以培养的习惯来帮助你避免攻击。第一个是停止盲目升级
00:08:25一切。你可能以前运行过 npm update 或类似命令,并且一口气
00:08:30将所有依赖项升级到了最新版本,但那正是这些攻击者
00:08:35所希望的行为,所以实际上要仔细审查这些升级并问问自己为什么需要
00:08:39升级它。第二个习惯正变得越来越重要:使用更少的包。你添加的每一个
00:08:43依赖都是一个攻击面,而大多数此类攻击通过依赖的依赖进行传播,
00:08:48所以问问自己为什么需要那个库真的非常有必要。我见过的常见例子包括
00:08:53像 Lodash 用于只需几行代码就能完成的功能,或者 Axios,其实 fetch
00:08:58也可以做同样的事情。我之所以说它正变得越来越重要,是因为在代理式(agentic)
00:09:03编码时代,让 AI 为你编写几个函数而不是使用一个依赖项是非常容易的。
00:09:08第三个习惯是将依赖项锁定到精确版本,这样升级总是一个审慎的
00:09:12选择。但要知道,这实际上只锁定了你在 package.json 中选择的包,
00:09:17而你依赖项的依赖项仍然可以使用它们自己的版本范围,这正是为什么第 1 个技巧中提到的冷却期
00:09:21很重要。所有这些结合在一起,应该能让你安心一点,不必担心
00:09:25意外安装被攻破的包。它们不是无敌的,但总比什么都不做要好。
00:09:29终极技巧是在强化开发容器中运行一切,但我一直觉得
00:09:34那样有点麻烦,所以最终还是回归了本地开发。如果你知道任何其他
00:09:38保护自己免受此类攻击的方法,请在下方的评论区告诉我,在你看的时候
00:09:42顺便订阅,一如既往,我们在下一期再见。

Key Takeaway

通过在包管理器中配置发布时效限制、强制使用锁文件检查以及部署如 Socket Firewall 的审计工具,可显著降低安装恶意供应链包的风险。

Highlights

  • 在 .npmrc、pnpm-config.yaml 或 bunfig.toml 中设置包发布时效限制,可以避开大多数在发布数小时内被发现的供应链攻击。

  • 禁用安装后脚本(install scripts)能防止恶意包在本地执行代码,PNPM 和 Bun 默认具备此功能,而 NPM 可通过 Lavamote 的 allow-scripts 实现。

  • MPQ 和 Socket Firewall 能够自动扫描并审计包的风险,甚至在安装前通过 AI 检测未知的潜在恶意行为。

  • 在 CI/CD 流水线中必须使用 npm ci 或带有 frozen-lockfile 标志的命令,以确保安装版本与锁文件严格一致,防止版本被篡改。

  • 限制 git URL 作为依赖源可以规避绕过注册表检查的攻击技巧,PNPM 的 block-exotic-sub-dependencies 设置可实现此目标。

Timeline

配置发布时效限制

  • 供应链攻击通常在发布后的极短时间内被发现,限制新包的安装时间窗口能有效规避风险。
  • NPM、PNPM 和 Bun 分别通过配置文件设置发布时效,单位分别为天、分钟和秒。

大部分恶意包在发布后的数小时内被标记。通过在 .npmrc 中设置日期,在 pnpm-config.yaml 中设置分钟数,或在 bunfig.toml 中设置秒数,安装程序将只获取超过指定期限的最新版本。注意 npx 和 bunx 通常不遵守此全局配置,仍会安装最新版本。

管理安装后脚本与依赖源

  • 禁用安装后脚本可阻断恶意包在安装阶段自动执行命令的手段。
  • 限制基于 Git URL 的依赖安装能防止攻击者绕过注册表审核。

包在安装后自动执行代码的功能常被恶意软件滥用。PNPM 和 Bun 默认对此进行限制,用户可使用 trustedDependencies 或相关标志实现白名单控制。同时,在 NPM 配置中设置 allow-git 为 none 或在 PNPM 中开启 block-exotic-sub-dependencies,可以阻止不可信的 Git 源依赖。

利用审计工具检查包安全

  • MPQ 和 Socket Firewall 能够在安装前扫描包的已知漏洞、拼写抢注风险及行为异常。
  • Socket Firewall 不仅支持 JavaScript,还扩展至 Python 和 Rust 生态。

这些工具通过为包管理命令创建别名来运作。它们会分析包的下载量、README 文件、维护者信息及注册表签名。Socket Firewall 特别受到重视,因为其 AI 模型能检测尚未人工审核的威胁,并已成功拦截过多次实际攻击。

确保锁文件安全与 CI 环境稳固

  • 锁文件应纳入版本控制,以防在 PR 中被恶意修改下载 URL。
  • CI 和生产环境强制使用 npm ci 或带有 frozen-lockfile 的安装命令,确保安装内容与锁文件完全一致。

锁文件若被篡改,会导致从恶意服务器拉取代码,且审查锁文件在 PR 中往往被忽略。使用 lockfile-lint 工具可验证锁文件解析出的 URL 是否合法。在 CI 环境中使用 clean install 命令,能够拒绝任何导致锁文件或 package.json 不匹配的安装尝试。

减少依赖与审慎升级习惯

  • 盲目升级所有依赖会增加暴露在被攻击版本下的风险。
  • 减少依赖数量直接缩小了项目的攻击面。

避免一次性运行更新命令,而应逐一审查升级原因。对于轻量级功能,优先考虑自己编写函数或使用原生 fetch API 代替庞大的第三方库。在代理式编码日益普及的背景下,减少不必要的依赖已成为降低供应链风险的必要手段。

Community Posts

No posts yet. Be the first to write about this video!

Write about this video