pnpm でサプライチェーン攻撃に備える

インフラ

npm エコシステムでは、正規のパッケージ名で悪意あるバージョンが公開される「サプライチェーン攻撃」が繰り返し起きている。個人ブログのような小さなプロジェクトでも、依存関係の数は数百に及ぶので他人事ではない。ここでは pnpm でできる対策をまとめる。

エイジゲート: minimumReleaseAge

もっとも効果が大きいのがこれだ。「公開されてから一定時間が経っていないバージョンはインストールしない」という設定で、悪意あるバージョンが検出・削除されるまでの猶予を稼げる。

yaml
# pnpm-workspace.yaml
minimumReleaseAge: 4320 # 分単位 = 3日

3日にしているのには理由がある。攻撃バージョンが金曜の夜に公開されると、週末を挟んで検出が遅れる。3日あればその週末をまたげる。

非レジストリ依存をブロック: blockExoticSubdeps

推移的な依存が git リポジトリや tarball の URL を直接指すのを禁止する。すべての依存をレジストリ経由に限定できる。

yaml
blockExoticSubdeps: true

信頼レベルの低下を検知: trustPolicy

パッケージの発行元の信頼レベルが以前より下がった場合(乗っ取りの兆候)にインストールを止める。

yaml
trustPolicy: no-downgrade

postinstall スクリプトを絞る: allowBuilds

pnpm はデフォルトで依存の postinstall スクリプトを実行しない。ビルドが必要なパッケージだけを明示的に許可する。

yaml
allowBuilds:
  esbuild: true

こうしておくと、これまでビルドを必要としなかったパッケージが、ある日突然スクリプトを実行し始めても気づける。

ロックファイルは必ずコミット

pnpm-lock.yaml をコミットしておけば、CI 環境(CI=true)では pnpm が自動で frozen install を行う。加えて package.jsonpackageManager フィールドで pnpm 自体のバージョンも固定しておくと安心だ。

新しい依存を足してポリシーに引っかかったら、minimumReleaseAge を緩めるのではなく pnpm clean --lockfile && pnpm install でロックファイルを作り直すのが正しい対処になる。

ゆべしま
フロントエンドエンジニア。型と開発体験の話が好き。週末は個人開発とコーヒー。