跳到主内容

pnpm 11.4

· 一分钟阅读
Zoltan Kochan
pnpm 的首席维护者

pnpm 11.4 关闭了有关锁文件完整性、凭证范围、git 解析、补丁文件和依赖项别名的一系列供应链漏洞,默认情况下使让 tarball 完整性不匹配导致安装硬失败(通过具有狭窄范围的“--update-checksums”选入),并将 pnpm runtime set 更改为默认写入devEngines.runtime 而不是 engines.runtime

次要更改

Tarball 完整性不匹配现在是一个硬性失败

此前,当下载的 tarball 哈希值与锁文件不匹配时,pnpm install(非锁定模式)会记录 ERR_PNPM_TARBALL_INTEGRITY 错误,静默地从注册源重新解析,并覆盖锁定的完整性校验值。 因此,即便项目已提交了锁定文件,受损的注册表、代理服务器或被重新发布的版本仍可能在干净的机器上用攻击者控制的内容进行替换。

pnpm install 现在会以 ERR_PNPM_TARBALL_INTEGRITY 错误退出,并提示使用新的可选标志。

唯一需要显式启用的操作是 pnpm install --update-checksums —— 其作用范围仅限于根据注册源当前提供的内容,更新锁定的完整性校验值。 它镜像了与 Yarn 同名的标志。 当旁路生效时,系统仍会输出警告信息,以便对该操作进行审计。

--forcepnpm update 特意跳过完整性检查。 这些属于常规刷新操作;若在这些流程中静默覆盖已锁定的完整性信息,将会抹除已提交的锁文件本应提供的保护作用。 --frozen-lockfile 的行为保持不变。 --fix-lockfile 保留了其既定用途(补全缺失的锁文件条目),且并非一种绕过机制。

pnpm runtime set 默认写入 devEngines.runtime

pnpm runtime set <name> <version> 现在默认将运行时保存到 devEngines.runtime,而不是 engines.runtime。 传入 --save-prod(或 -P)以将其保存到 engines.runtime。 请参阅 #11948

补丁更改

安全性:无作用域凭据不再跨注册源泄露

如果在某个来源(如 ~/.npmrc~/.config/pnpm/auth.ini、工作区 .npmrc、CLI 标志等)中定义了未限定作用域的 _authToken(或 _authusername + _passwordtokenHelper),那么该凭证将作为 Authorization 请求头,发送给由另一个(可能不受信任的)来源所指定的任何注册源。 同样的暴露风险也延伸到了客户端 TLS 凭证(certkey)。

pnpm 现在会在加载时,将每个未限定作用域的“每注册源”配置项(包括 _authToken_authusername_passwordtokenHelpercertkey)重写为限定 URL 作用域的形式;重写时会使用同一来源中声明的 registry= 值(若该来源未声明,则使用 npmjs 的默认注册源)。 因此,后续层中通过 registry= 进行的覆盖无法沿用未限定作用域的凭证,因为该凭证已被锁定在创建者指定的 URL 上。 cacafile 特意未被限制作用域——它们属于信任锚点而非凭证,而 MITM 代理配置正是依赖它们在全局范围内生效的。

每次重新设定作用域时,都会发出一条弃用警告,告知用户该设置是在何处被锁定的,以及如何直接编写该设置。 自 npm@9 起,npm 已彻底拒绝使用无作用域的凭证;pnpm 也计划在未来的主版本更新中移除对此类凭证的支持。 若要针对特定​​注册源,请按 URL 作用域编写该设置:

.npmrc
//registry.example.com/:_authToken=...
//registry.example.com/:cert=...

安全性:缺少 integrity 字段的锁文件条目将被拒绝

此前,负责解压已下载 tarball 的工作进程在未提供完整性校验信息时,会跳过哈希验证,并直接根据未经校验的字节数据生成一个新的哈希值。 如果攻击者既能修改锁文件(例如通过移除 integrity: 字段的 Pull Request),又能针对引用的 tarball URL 提供被篡改的内容,那么他​​们就能在不触发任何错误的情况下安装被篡改的包——即使在使用 --frozen-lockfile 选项时也是如此。

pnpm 现在在读取锁文件时,若遇到 ERR_PNPM_MISSING_TARBALL_INTEGRITY 错误,会采取“失败并终止”的策略。 托管于 Git 平台的 tarball(设置了 gitHosted: true 或使用 codeload.github.com / bitbucket.org / gitlab.com 上的 URL)以及 file: 协议的 tarball 不受此限制——因为 Git 托管 URL 中的提交 SHA 以及用户指定的本地路径已经锁定了具体内容。

安全性:git 解析拒绝非 SHA 格式的 commit 字段

如果 Git 解析的 commit 字段不是 40 字符的十六进制 SHA 值,则会在调用 git 之前被拒绝。 否则,恶意锁文件可能会通过 git fetchgit checkout 注入诸如 --upload-pack=<command> 之类的值;在使用 SSH 或本地文件传输协议时,这会导致执行该值所指定的命令。

安全性:写入软件包目录之外的补丁文件将被拒绝

如果补丁文件的 diff --git 头部引用了被修补软件包目录之外的路径,这些补丁文件现在会被拒绝。 此前,通过 Pull Request 引入的恶意 .patch 文件,能够对运行 pnpm install 的用户有权访问的任意文件执行写入、删除或重命名操作。

安全性:包含路径遍历片段的依赖项别名将被拒绝

当从包清单读取或通过符号链接添加进 node_modules 时,包含路径遍历片段(例如 @x/../../../../../.git/hooks)的依赖别名会被拒绝。 否则,恶意注册源包可能会利用传递依赖项的键,导致 pnpm install 在预期的 node_modules 目录之外、由攻击者指定的路径上创建符号链接。

受信任发布者的元数据现要求提供来源信息

只有在同时具备来源信息的情况下,受信任发布者的元数据才会被视为最强有力的信任依据。

其他修复

  • 修复了当 config Dependencies 声明了 paquet(即 paquet@pnpm/paquet)时,执行 pnpm deploy 导致 ENOENT: ...lstat '<0>/node_modules' 崩溃的问题 。 部署目录从不安装配置依赖项,因此它们指定的安装引擎并未存在于磁盘上供调用;现在的嵌套安装过程会跳过这些依赖项。
  • 在列出大型工作区时,限制并发读取项目清单的操作,以避免出现 EMFILE 错误。
  • onFail 设置为 errorwarn 时,验证 nodedenobundevEngines.runtimeengines.runtime 版本范围。 此前,这些设置仅在 onFail: 'download' 模式下生效——errorwarn 模式则不会产生任何实际效果 #11818。 违规情况现在会抛出 ERR_PNPM_BAD_RUNTIME_VERSION 错误。
  • 改进当设置了 minimumReleaseAge 但未设置 minimumReleaseAgeStrict 时,pnpm 自动向 minimumReleaseAgeExclude 添加条目后所打印的日志信息。 此前,相关提示信息使用了内部术语“松散模式”,导致用户无法在文档中查找到相关说明;现在,如果用户希望将这些更新置于提示确认之后再进行,系统会提示将 minimumReleaseAgeStrict 设置为 true #11747