- Shai-Hulud 2.0 の悪意ある npm パッケージが開発者マシンを感染させ、Trigger.dev の GitHub 組織アクセス権を奪取した事件が発生
- 感染は開発者が
pnpm install を実行した際、悪意あるパッケージの preinstall スクリプトが実行されることで始まり、TruffleHog ツールを用いて認証情報を窃取
- 攻撃者は17時間にわたり 669個のリポジトリを複製し、その後10分間で 199個のブランチへの強制プッシュと42件のPRクローズを試行
- パッケージと本番システムは損なわれておらず、攻撃は4分で検知されてアカウントアクセスは遮断された
- 事件後、npm スクリプトの無効化、pnpm 10 へのアップグレード、OIDC ベースの npm 配布、ブランチ保護の全面適用など、セキュリティ体制を強化
攻撃の概要
- 2025年11月25日、内部 Slack でのデバッグ中に、Linus Torvalds 名義の「init」コミットが複数のリポジトリで作成される異常な兆候が発生
- 調査の結果、Shai-Hulud 2.0 サプライチェーンワームが開発者マシンを感染させ、GitHub 認証情報を窃取していたことが確認された
- このワームは500個以上の npm パッケージを感染させ、25,000個以上のリポジトリに影響を与えたと報告されている
- Trigger.dev の公式 npm パッケージ(
@trigger.dev/*、CLI)は感染していない
攻撃タイムライン
- 11月24日 04:11 UTC: 悪意あるパッケージの配布開始
- 20:27 UTC: ドイツの開発者マシンが感染
- 22:36 UTC: 攻撃者が初回アクセスし、大量のリポジトリ複製を開始
- 15:27〜15:37 UTC(11月25日): 10分間にわたり破壊的攻撃を実行
- 15:32 UTC: 異常を検知し、4分以内にアクセスを遮断
- 22:35 UTC: すべてのブランチの復旧完了
感染の過程
- 開発者が
pnpm install を実行すると、悪意あるパッケージの preinstall スクリプトが実行され、TruffleHog をダウンロードして実行
- TruffleHog は GitHub トークン、AWS 認証情報、npm トークン、環境変数などをスキャンして外部へ流出させた
- 感染したマシンから
.trufflehog-cache ディレクトリと関連ファイルが発見された
- 感染原因となったパッケージは削除されており、追跡は不可能
攻撃者の活動
- 感染後17時間にわたり 偵察活動を継続
- 米国およびインドのインフラを利用して669個のリポジトリを複製
- 開発者の活動を監視し、GitHub トークンによるアクセスを維持
- 「Sha1-Hulud: The Second Coming」という名前のリポジトリを作成し、認証情報保存に使われたとみられる
- その後10分間で 破壊的行為を実行
- 16個のリポジトリで199個のブランチへの強制プッシュを試行
- 42件の PR をクローズし、一部はブランチ保護設定によって阻止された
- すべてのコミットは「Linus Torvalds <email> / init」の形式で表示
検知と対応
- Slack アラートを通じて異常な兆候をリアルタイムで検知
- 4分以内に感染アカウントの GitHub アクセスを遮断し、その後 AWS・Vercel・Cloudflare などすべてのサービスへのアクセスを取り消し
- AWS CloudTrail ログの分析結果では、読み取り専用 API 呼び出しのみが存在し、本番データへのアクセスはなかった
- AWS は別途 Shai-Hulud 関連の疑わしい挙動を検知し、警告を送信
被害と復旧
- 669個のリポジトリ複製、199個のブランチへの強制プッシュ、42件の PR クローズ
- GitHub のサーバー側 reflog が存在しないため復旧は困難だったが、イベント API とローカル reflogを活用して7時間以内に全面復旧
- npm パッケージと本番インフラは損なわれていない
GitHub App キーの露出
- 調査中、開発者ノートPCのゴミ箱から GitHub App の秘密鍵が発見された
- この鍵は顧客リポジトリに対して read/write 権限を持っており、ただちにローテーション措置を実施
- データベース(インストール ID 保存)は損なわれておらず、顧客リポジトリへのアクセスの証拠はないが、完全には排除できない
- GitHub サポートチームに追加ログを要請し、顧客にはメールで告知を送付
Shai-Hulud の技術分析
setup_bun.js 実行時に Bun ランタイムをインストールし、バックグラウンドで bun_environment.js を実行
- TruffleHog を使って
$HOME ディレクトリ内の認証情報を収集
- 収集したデータ(
contents.json、cloud.json、truffleSecrets.json など)を ランダムな GitHub リポジトリへ3重 base64 エンコードの形でアップロード
- npm トークンが存在する場合、感染したアカウントのパッケージを改変・再配布してワームを拡散
- 認証情報がない場合は ホームディレクトリの削除を試行
- 感染指標ファイル:
setup_bun.js、bun_environment.js、.trufflehog-cache/ など
セキュリティ強化措置
- npm スクリプトを全面的に無効化(
ignore-scripts=true)
- pnpm 10 にアップグレード: スクリプトをデフォルトで無効化し、
minimumReleaseAge(3日)設定で新規パッケージのインストールを遅延
- OIDC ベースの npm Trusted Publishers を導入して長期トークンを排除
- すべてのリポジトリにブランチ保護を適用
- AWS SSO に Granted を導入し、セッショントークンを暗号化
- GitHub Actions で外部コントリビューターのワークフロー実行時に承認必須へ変更
他チームへの教訓
- npm インストール時に実行される 任意コード実行の仕組み自体が攻撃面である
ignore-scripts=true の設定と、必要なパッケージだけをホワイトリスト管理することが必要
- pnpm minimumReleaseAge によって新規パッケージのインストールを遅延
- ブランチ保護とOIDC ベースのデプロイは必須のセキュリティ対策
- ローカルマシンに長期認証情報を保存せず、CI 経由のデプロイのみを許可すべき
- Slack アラートのノイズが検知の鍵となった
人間的側面
- 感染した開発者に落ち度はなく、単に
npm install を実行しただけで被害が発生した
- 攻撃中、そのアカウントが数百個のランダムなリポジトリに自動で「star」を付けた痕跡も見つかった
- この事件は個人のミスではなく、エコシステムの構造的脆弱性を浮き彫りにした
要約指標
- 初回感染から最初の攻撃まで: 約2時間
- 攻撃者のアクセス維持時間: 17時間
- 破壊行為の継続時間: 10分
- 検知まで5分、遮断まで4分
- 全面復旧完了まで7時間
- 複製されたリポジトリ: 669個 / 影響を受けたブランチ: 199個 / クローズされた PR: 42件
参考リソース
- Socket.dev: Shai-Hulud Strikes Again V2
- PostHog、Wiz、Endor Labs、HelixGuard の分析レポート
- npm Trusted Publishers、pnpm
onlyBuiltDependencies、minimumReleaseAge、Granted ドキュメント
3件のコメント
pnpm はデフォルトで post-install を個別に許可しなければならない仕組みだったはずですが、結局は開発者も無意識のうちに許可してしまうようですね
npmはデフォルトで実行されるため、pnpmに切り替えてデフォルトで無効化するように変更し、セキュリティを強化したという理解です。Hacker Newsの意見
npm installを実行すること自体は 怠慢 ではない問題は、パッケージのインストール時に 任意コード実行を許してしまうエコシステム にある
しかし根本的なセキュリティ上の失敗は、第三者が何の検証もなく自分の製品にコードを流し込めるパッケージマネージャを使っていることだ
結局のところ、私たちはパッケージマネージャとその運営者の善意と能力に無限に依存している
それに OP が認証情報を平文でファイルシステムに保存していたことを示唆しているようにも見える
言語レベルで、コードが入力を読み取り、リソースを消費し、型的に正しい出力だけを生成するよう制限する構造は作れる
サプライチェーン問題の完全な解決にはならないが、露出範囲はかなり小さくなる
「一人がこうしたツールを使うのは悪くないが、みんなが使うとエコシステムが問題になる」という話になっている
多くの開発ツールが セキュリティ的に脆弱 だということは、すでに何度も証明されている
本当に気にしているなら、行動で示すべきだ
VS Code を使っていたとき、ちょっとした機能を追加するにも、誰が作ったのかも分からないプラグインを入れなければならず不便だった
結局 信頼するしかないコード を実行する構造になるのではないかと思う
netrcファイルしか対応していないgitを HTTP で使うなら、この種の 平文認証情報の露出経路 はほぼ常に存在するpnpm のメンテナが 1 年前に「post-install スクリプトをデフォルトでブロック しよう」と提案していた
ユーザーにとっては不便になるだろうが、長期的には誰もがありがたく思う変更だと信じていた
関連 PR: pnpm/pnpm#8897
結局 利便性がセキュリティに勝った また一つの事例だ
「データベースは侵害されなかった」と言っているが、攻撃者が AWS とシークレットにアクセス していたなら、すでに侵害されたと見るべきだ
アクセス可能性があったなら侵害と見なすべきだ
マルウェアが実行された後では、出所の追跡はほぼ不可能 だ
pnpm installも正常に完了するので検知しにくいSentinel One や CrowdStrike のような EDR があれば、調査の手がかりはもっと多かったはずだ
「合計 669 個の repo をクローンした」という部分が目についた
従業員が 100 人にも満たない会社が 600 を超える repo を持っているのが普通なのか気になる
チーム規模よりも 年数とプロジェクト寿命 のほうが repo 数に大きく影響する
pnpm はすでに
preinstallのような ライフサイクルスクリプトの自動実行を停止 していたので、古いバージョンを使っていたようだ関連 PR 参照
postinstallスクリプトがあった可能性もあるpnpm は依存関係のスクリプトは防ぐが、プロジェクトレベルのスクリプトは依然として実行する
透明性のある 事後分析(post-mortem) の共有に感謝する
こうした事例は業界全体にとって重要だ
攻撃トラフィックが通常の開発トラフィックと区別可能だったのか気になる
こちらでも dev 環境で egress フィルタリング を強化しようとしているが、
npm installが頻繁に壊れるので悩ましい個人ノート PC の
gitセキュリティについて考えている今は SSH キーをローカルに置いて
pushしている管理者権限もあるので危険だ。もっと安全にする方法が知りたい
push/pullする構成を勧めるこうすると 署名キーとアクセスキーを分離 でき、管理者アカウントも別管理にできる
関連文書: 1Password SSH Agent, Git Commit Signing, GitHub OAuth, GitHub CLI Login
キーを外部に流出させられないという利点はあるが、マルウェアが自分のマシン上で動けば依然として危険だ
Linux の例, macOS の例
TPM や Yubikey で多少難しくはできても、完全な防御は不可能だ
管理者作業は専用の別マシンで行うのが安全だ
gpg-agentで SSH 認証を行う方法もあるpushやcommit時に PIN 入力でロック解除するpushを防ぎ、MFA を必須化 すれば、攻撃者がすぐにデプロイブランチへアクセスするのは難しくなるTorvalds 名義のコミットは、感染後によく見られる 目印(signature) だった
Microsoft の 公式分析記事 でも言及されている
このワームは非常に騒々しく、一部の攻撃者は露出した認証情報を使って 非公開 repo を公開したり README を改変 したりして宣伝目的に悪用していた
攻撃者が破壊行為をせず 静かに情報だけを流出 させていたら、こうした検知が可能だったのか疑問だ