以下の内容はhttps://creators.bengo4.com/entry/2026/01/26/080000より取得しました。


クラウドサインは npm から pnpm へ移行しました

はじめに

こんにちは、クラウドサインでフロントエンドエンジニアをしています篠田 (@tttttt_621_s) です。

普段のソフトウェア開発において、OSS の活用は欠かせません。クラウドサインも多くの OSS に支えられています。

しかし一方で、OSS には一定のリスクもあります。2025 年を振り返ると、npm パッケージにおけるサプライチェーン攻撃がたびたび話題になりました。

本記事では、サプライチェーン攻撃への対策の 1 つとして npm から pnpm へ移行した経緯とその過程で得た知見を共有します。

モチベーション

移行を決めた理由はいくつかありましたが、最大の理由は、先にも触れたサプライチェーン攻撃への対応です。

2025 年 8 月に発生した PhantomRaven と呼ばれる攻撃では、HTTP URL 経由で外部パッケージを動的に取得する手法が使われました。 これにより npm のセキュリティスキャンを回避し、クリーンなパッケージに見せかけることが可能になり、GitHub トークンや CI 認証情報などが窃取されました。

2025 年 9 月に発生した Shai-Hulud と呼ばれる攻撃では、npm のライフサイクルスクリプト (postinstall など) が悪用されました。 npm ではパッケージのインストール時にこれらのスクリプトが自動的に実行されます。 攻撃者はこの仕組みを利用し、インストール時に認証情報を窃取するスクリプトを仕込み、盗んだ npm トークンを使って他のパッケージにも感染を広げるワーム型の攻撃でした。

こうした脅威に対して、pnpm はライフサイクルスクリプトの実行をデフォルトで制限し、外部ソースからの依存関係をブロックする機能を提供しています。 これが移行を決断した大きな理由です。

pnpm が提供するセキュリティ機能

pnpm 移行にあたって以下の機能を活用しました。

strictDepBuilds

https://pnpm.io/settings#strictdepbuilds

pnpm では、依存パッケージのライフサイクルスクリプトがデフォルトで実行されません。

strictDepBuildstrue に設定すると、後述する allowBuilds で管理されていないライフサイクルスクリプトを持つ依存関係がある場合はインストールが失敗します。

strictDepBuilds: true

allowBuilds

https://pnpm.io/settings#allowbuilds

ライフサイクルスクリプトの実行を許可 / 禁止するパッケージを明示的に指定します。 pnpm 10.26 で追加された機能で、以前の onlyBuiltDependenciesignoredBuiltDependencies を置き換えるものです。

strictDepBuilds と組み合わせることで、特定のパッケージのみがライフサイクルスクリプトを実行できるようになり、それ以外はインストールを失敗させる厳格な運用が可能になります。

allowBuilds:
  esbuild: true
  core-js: false

trustPolicy

https://pnpm.io/settings#trustpolicy

パッケージの信頼レベルとして以下がありますが、 trustPolicyno-downgrade に設定すると、信頼レベルが以前のリリースよりも低下した場合にインストールを失敗させることができます。

  1. Trusted Publisher: GitHub Actions + OIDC トークン + npm provenance で公開
  2. Provenance: CI/CD システムからの署名付き証明書がある
  3. No Trust Evidence: ユーザー名/パスワードまたはトークン認証で公開

実際にプロダクトに導入すると、何らかのパッケージの依存関係でインストールが失敗することがあると思います。 その場合は後述する trustPolicyExclude で除外することが可能です。

trustPolicy: no-downgrade

trustPolicyExclude

https://pnpm.io/settings#trustpolicyexclude

trustPolicy のチェックから除外するパッケージを指定します。trustPolicy の要件を満たさない場合でも例外としてインストールを許可したいパッケージがある場合に使用します。

クラウドサインでは、trustPolicyExclude へのパッケージ追加は最小限にして、追加する場合は背景情報をコメントで補足するようにしています。

trustPolicyExclude:
  # コメント例:
  # sass の依存関係で "chokidar": "^4.0.0" が入っている
  # chokidar は 4.0.2 から attestations がなくなっているので除外している
  - chokidar@4.0.3

blockExoticSubdeps

https://pnpm.io/settings#blockexoticsubdeps

blockExoticSubdepstrue に設定すると、Git リポジトリ (git+ssh://...) や tarball URL (https://.../package.tgz) などの、npm レジストリ以外からの依存関係をブロックできます。

blockExoticSubdeps: true

実際の移行手順

移行は大まかに以下の流れで行いました。

  1. pnpm import で既存の package-lock.json から pnpm-lock.yaml を生成 (npm で使っていたのと同じバージョンを維持するため)
  2. pnpm-workspace.yaml にセキュリティ設定を追加
  3. Lefthook (git hooks) の更新
    • npm runpnpm run
    • npxpnpm exec
  4. GitHub Actions の更新
    • pnpm/action-setup アクションの追加
    • actions/setup-node のキャッシュ設定を npm から pnpm に変更
    • npm cipnpm install --frozen-lockfile

移行してみての所感

思っていたよりもスムーズに移行できました。pnpm import のおかげで既存の package-lock.json をそのまま活用でき、依存関係の再解決による予期しない変更を避けることができました。

当初の目的であったサプライチェーン攻撃への対策という観点では、以下の点で効果を実感しています。

  • strictDepBuildsallowBuilds により、ライフサイクルスクリプトを実行するパッケージが明示的に管理されるようになった
  • trustPolicy により、パッケージの信頼レベルが低下したパッケージの導入を防げるようになった
  • blockExoticSubdeps により、npm レジストリ以外からの依存関係が混入するリスクを排除できた

まとめ

npm パッケージを悪用したサプライチェーン攻撃への対策の 1 つとして、npm から pnpm へ移行しました。

pnpm ではさまざまなセキュリティ機能が提供されており、ライフサイクルスクリプトの制限や外部ソースからの依存関係のブロックが可能です。 さらに今後のアップデートでは今回紹介した strictDepBuildsblockExoticSubdeps の設定がデフォルトで有効になりそうです。(参考 PR

余談ですが、pnpm には minimumReleaseAge (公開から一定時間経過しないとインストールできない機能) もあります。 クラウドサインでは普段のパッケージのアップデートは Renovate で行っており、Renovate の minimumReleaseAge で同等の制御を担保しています。 これによりセキュリティパッチなど緊急のアップデートが必要な場合に手動で即座に対応できるようにしています。

セキュリティを重視したパッケージ管理に興味がある方は、ぜひ pnpm への移行を検討してみてください。

最後までお読みいただきありがとうございました。




以上の内容はhttps://creators.bengo4.com/entry/2026/01/26/080000より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14