9月に発生した npm のサプライチェーン攻撃 | 導入した最低限の対策まとめ
9月に、npm エコシステムを狙った大規模なサプライチェーン攻撃が発生しました。
改ざんされたパッケージが postinstall スクリプトとして認証情報を盗み取る仕組みで、
Node.js / npm を使う開発者であれば決して他人事ではない内容でした。
今回のブログでは、私が普段の開発プロジェクトで導入した、「npm install を安全に行うための最低限の対策」 をまとめます。
最低限の対策ですが、やらないよりは確実に良いので共有します
9月の npm サプライチェーン攻撃とは
今回の攻撃は、メンテナーのアカウントが侵害され、改ざんされたパッケージの新バージョンが npm に publish されることで広がっていきました。
利用者側は、普通に npm install をしただけで、内部で悪意ある postinstall が実行される危険がありました。
特にやっかいなのが、自分が直接インストールしていない “間接依存” からも感染する可能性があること。
npm install が実質「任意コードの実行」であることを改めて実感させられる事件でした。
npmが発表した対策
今回の攻撃を受け、npmから次のような対策方針が発表されました。
- npm publish 時の 2FA(多要素認証)強化
- Classic Token の廃止、権限を細分化したトークン+7日間の有効期限へ移行
- TOTP から FIDO/WebAuthn へ移行
- Trusted Publishing(署名ベース)の推奨
詳しく知りたい人は GitHub Blog や CISA の発表を参照してください。
9月時点での初動対応
攻撃直後は情報が錯綜しており、
「どの依存が影響するのか」もわかりにくい状況でした。
そのため、まずはリスクを避けるために 一時的に npm install を停止し、
npm 側の情報・対策が出揃うまで依存アップデートも控える判断をとりました。
無理に動くより、まず状況を見極める方が安全と判断しました。
npm install の危険性をあらためて認識した
npm install は単なる「パッケージを入れる作業」ではなく、
postinstall で任意コードが実行される可能性を持った操作です。
今回の件で、「インストール前に安全性を確認するフロー」が必要だと強く感じました。
ここから、次のステップとして自分のプロジェクトに対策を導入し始めました。
導入した対策:npm install を安全に行うフロー
まず最初に、今回導入したフローの全体像です。
npm run safe-installで以下を実行出来るようにしました。
npm install --package-lock-onlyで lockfile のみ更新するnpm audit --audit-level=criticalで脆弱性をチェックする- 問題なければ依存をインストールする
この「インストール前に一度止めて、lockfile ベースで安全性を確認する」のがポイントです。
ここから、それぞれのステップを少しだけ詳しく書きます。
① npm install --package-lock-only で lockfile のみ更新
普段の npm install はそのまま node_modules を更新し、postinstall が走る可能性があります。
そのため、まずは依存の解決だけを行い、ライブラリをまだインストールしない状態 にします。
npm install --package-lock-only
- node_modules は一切更新されない
- パッケージの実行も行われない
- 悪意の postinstall を防げる
- インストール前の状態で安全性をチェックできる
この “止める” という段階がとても重要です。
② npm audit --audit-level=critical で安全性をチェック
更新された lockfile をもとに、まずは 最重要(critical)レベルの脆弱性だけ 検出します。
npm audit --audit-level=critical
当初は high レベルで検出する予定でしたが、
ライブラリのアップデートに時間を要するものもあったので 一旦 critical のみに絞っています。
依存の整理が進みしだい、high まで引き上げる予定です。
③ 問題がなければ install
安全だと判断できた場合のみ、最終的に依存をインストールします。
CI にも npm audit を組み込む
ローカルで “安全な npm install フロー” を導入するだけでも最低限の対策になりそうですが
これだけではまだ不十分だと感じました。
依存ライブラリは 後日に脆弱性が発表される ことも多く、 また、Dependabot も動いているため 依存が更新されるタイミングで最新の情報を使って検査したい というニーズがあります。
そこで、CI にも npm audit チェックを組み込みました。
これにより、 手動の依存更新(開発者の PR) 自動の依存更新(Dependabot) どちらの場合でも、 その時点での最新の脆弱性情報を使って安全性を確認できる仕組み になっています。
つまり、PR 作成時に必ずチェックが走るため、 後日判明した脆弱性も検出できる運用 になりました。
jobs:
install:
...割愛
# ① lockfile だけ更新
- name: Update package-lock.json
run: npm install --package-lock-only
# ② 最新の脆弱性情報で検査
- name: Run npm audit
run: npm audit --audit-level=critical
# ③ 問題なければ依存をインストール
- name: Install dependencies
run: npm ci
補足:この対策の限界と、追加で導入した対策
ここまで紹介した npm audit ベースの方法は「最低限」の対策です。
正直に言うと、今回の攻撃パターンに対しては十分とは言えません。
npm audit の限界
npm audit が検出できるのは 「既知の脆弱性データベースに登録されたもの」だけ です。
9月の攻撃のように「改ざんされた直後のパッケージ」は、
データベースに登録されるまで数時間〜数日かかります。
つまり、攻撃が最も危険なタイミングでは検出できない という弱点があります。
追加で導入した対策
--ignore-scripts で postinstall を無効化する
npm audit の限界を補うため、.npmrc に以下を追加しました。
ignore-scripts=true
これにより、postinstall 自体が実行されなくなります。
攻撃の実行経路そのものを断つ、最も直接的な対策です。
ただし esbuild や sharp など、postinstall でバイナリをダウンロードするパッケージは動かなくなります。
その場合は npm rebuild <パッケージ名> で個別に対応してください。
最新バージョンをすぐにインストールしない
新しいバージョンが出ても、すぐに飛びつかない。
攻撃されたパッケージは数時間〜数日で発覚・削除されることが多いです。
数日〜1週間ほど様子を見てからアップデートするだけで、
リスクの高い期間を避けられます。
Dependabot の自動マージも避けたほうが安全です。
PRが来たら、少し寝かせてからマージする運用を検討してください。
まとめ
今回の攻撃をきっかけに、npm install をより安全に行うためのフローを導入しました。
小さな改善ですが、依存更新時のリスクを減らすには十分な効果があります。
高度な仕組みではありませんが、
開発フローを大きく変えずに安全性を高められる実用的な方法だと感じています。
この記事が同じように対策を検討している方の参考になれば幸いです。
Discussion