blog.re-taro.dev
投稿日

pnpm とセキュアな依存管理

pnpm v10 から、依存するパッケージのライフサイクルスクリプトをデフォルトで実行しないようになる。

昨今、swc や esbuild など異なる言語で書かれたパッケージを npm でインストールすることが一般的になってきた。 これらは、バイナリで提供されているため、インストールしたユーザーのアーキテクチャに合わせて用意される必要がある。

swc/packages/core/postinstall.js at f960d52364e72fa7548cc8aaaf6367dfdf7b9a8f · swc-project/swc · GitHub

Rust-based platform for the Web. Contribute to swc-project/swc development by creating an account on GitHub.

github.com
Rust-based platform for the Web. Contribute to swc-project/swc development by creating an account on GitHub.

このコードは。swc の postinstall script で、インストール時に実行される。 内容は、適切なアーキテクチャのバイナリを検証し、無い場合は @swc/wasm を取得してくるというものである。

npm レジストリのパッケージは度々サプライチェーンの標的にされる。12

ここで読者の皆さんに質問したい。あなたはある npm パッケージをインストールするときに、そのパッケージがどのようなライフサイクルスクリプトを持っているかを確認しているだろうか? 私はしていない。

前述した通り、ライフサイクルスクリプトを必要とする npm パッケージは必要な理由がある。 きっと読者含め多くの人が、その理由を理解しているからこそ必要とされるパッケージに、ライフサイクルスクリプトがあってもおかしくないな、という気持ちを持っているだろう。

しかし、やべーハカーは、ライフサイクルスクリプトを悪用する。 直近にも、npm パッケージのライフサイクルスクリプトを悪用した脆弱性が報告されている。3

これは余談だが、rspack はどうやらライフサイクルスクリプトを必要としないらしい。 対応していないアーキテクチャで rspack を使おうとすると例外が投げられて死ぬ。

rspack/crates/node_binding/binding.js at cc5e3f6a49073b354c08ec4b8c75142e602d02ee · web-infra-dev/rspack · GitHub

The fast Rust-based web bundler with webpack-compatible API 🦀️ - rspack/crates/node_binding/binding.js at cc5e3f6a49073b354c08ec4b8c75142e602d02ee · web-infra-dev/rspack

github.com
The fast Rust-based web bundler with webpack-compatible API 🦀️ - web-infra-dev/rspack

Release pnpm 10 · pnpm/pnpm · GitHub

Fast, disk space efficient package manager. Contribute to pnpm/pnpm development by creating an account on GitHub.

github.com
Major Changes


Lifecycle scripts of dependencies are not executed during installation by default! This is a breaking change aimed at increasing security. In order to allow lifecycle scripts of spe...

pnpm v10 では Node.js から corepack が分離する決定4に伴い、package.json#packageManager を見て pnpm が指定されているプロジェクトでは、そのプロジェクトの pnpm を使う挙動がデフォルトとなった。

詳しい話は euxn23 さんの記事を読んでほしい。めちゃ分かりやすい。

pnpm v10 で corepack 不要で pnpm 自身のバージョン管理が可能に

zenn.dev

他にも store バージョンの更新や、pnpm link の挙動変更など、多くの変更があるが、本記事ではライフサイクルスクリプトの実行周りについてのみ触れる。

feat!: use an allow list of built dependencies by default by zkochan · Pull Request #8897 · pnpm/pnpm · GitHub

By default no dependency is allowed to run lifecycle scripts during installation.

github.com
By default no dependency is allowed to run lifecycle scripts during installation.

直近に起こった rspack の脆弱性を受けて、pnpm team は改めて Twitter でライフサイクルスクリプトの実行についてユーザーアンケートを募った。5

その結果、ライフサイクルスクリプトの実行をデフォルトで無効化することが決定され、実装された。

リリースノート にも記載されている通り、特定の依存関係のライフサイクルスクリプトを許可するには、package.json に以下のように追記することで可能となる。

{
	"pnpm": {
		"onlyBuiltDependencies": ["fsevents"]
	}
}

この例では、fsevents というパッケージのライフサイクルスクリプトのみを許可する設定となる。

実装の PR では bun のアプローチとの違いについて議論されている。

bun も pnpm と同様にライフサイクルスクリプトの実行をデフォルトで無効化しているが、bun はデフォルトでライフサイクルスクリプトの実行を許可するパッケージのリストを持っている。

bun/src/install/default-trusted-dependencies.txt at 8c75c777c2ffa418b18f313fcde23df4c147e964 · oven-sh/bun · GitHub

Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one - bun/src/install/default-trusted-dependencies.txt at 8c75c777c2ffa418b18f313fcde23df4c147e964 · oven-sh/bun

github.com
Incredibly fast JavaScript runtime, bundler, test runner, and package manager – all in one - oven-sh/bun

実装の PR では bun のようにデフォルトで許可するパッケージのリストを持つことを求める声もあったが、pnpm はそのようなアプローチを取らなかった。

pnpm team はリストのメンテナンスが大変であることや冗長であることなどから、ユーザーが明示的に許可するパッケージを指定することで、より安全な環境を提供することを目指した。

pnpm v10 で依存のライフサイクルスクリプトの実行は制限されたが、onlyBuiltDependencies に設定している配列を良い感じに管理することは難しい。

そこで pnpm v10.1 では新たに pnpm ignored-buildspnpm approve-builds というコマンドが追加された。

feat: add ignored-builds command by zkochan · Pull Request #8963 · pnpm/pnpm · GitHub

Related PR: feat!: use an allow list of built dependencies by default #8897

github.com
Related PR:

feat!: use an allow list of built dependencies by default #8897

pnpm ignored-builds は、ライフサイクルスクリプトを持った依存関係を一覧表示するコマンドである。 pnpm approve-builds は、ライフサイクルスクリプトを持った依存関係を許可するコマンドである。

これらのコマンドを使用することで、対話的にライフサイクルスクリプトを持った依存関係を管理することが可能となる。 (読んでみた感じ追加ができるだけで削除はできないようで、ほしいなぁと思うのだ)

pnpm v10 から、依存するパッケージのライフサイクルスクリプトをデフォルトで実行しないようになった。

JavaScript のエコシステムは日々進化している。その中で、セキュリティに関する問題も増えている。 すこしでもセキュリティを意識して、より安全な環境を提供するために、pnpm v10 での変更は大きな意味を持つ。


脚注