
自己増殖する npm ワーム Miasma と Phantom Gyp - binding.gyp が npm install を乗っ取る手口と防御
入力検証とコマンド注入の定番書。
設計段階から安全性を作り込む思想。
認証情報窃取に効くハードウェアMFA。
当サイトは Amazon.co.jp を宣伝しリンクすることで紹介料を得る手段を提供する、Amazonアソシエイト・プログラムの参加者です。価格・在庫はリンク先の最新情報をご確認ください。
2026年の夏、npm エコシステムを狙うサプライチェーン攻撃が新しい局面に入りました。これまで攻撃の主戦場だった preinstall/postinstall といったライフサイクルスクリプトを一切宣言しないのに、npm install の時点でコードを実行してしまう手口が観測されています。StepSecurity が「Phantom Gyp」と名付けたこの手口と、それを使って自己増殖する npm ワームMiasmaについて、一次調査に沿って防御側の視点で整理します。
2026年夏、止まらない npm ワームの波
2026年7月14日、API 仕様記述ツールで知られる AsyncAPI の公式パッケージ群が汚染されました。攻撃者が asyncapi/generator リポジトリへの push 権を奪い、正規のリリースパイプラインを起動して、正規の npm OIDC provenance(来歴証明)付きで悪性版を公開した、とされます。汚染されたのは @asyncapi/generator@3.3.1 などです(詳細は後述します)。
この7月の波は、6月に観測された別の手口と同じ Miasma ファミリーに属します。ただし発火の技術は異なります。まずは6月に登場した、install 時に発火する手口「Phantom Gyp」から見ていきます。過去の npm 汚染事例としてはaxios の npm サプライチェーン攻撃も参考になります。
これまでの主戦場: preinstall/postinstall と npm の対策
従来の npm サプライチェーン攻撃の多くは、package.json の scripts に preinstall/postinstall を書き、npm install の際に自動実行させる手口でした。この問題への対策として、npm 12 ではインストールスクリプトがデフォルトで無効化される方針が導入されています(npm 12: install scripts をデフォルト無効化)。pnpm も同様にライフサイクルスクリプトを既定でブロックする方向へ進んでいます。
つまり「怪しいのは postinstall だ」という前提が、防御側にも監査ツールにも共有されてきました。Phantom Gyp は、その前提そのものを外してきます。
Phantom Gyp の正体: binding.gyp と node-gyp rebuild
ネイティブアドオン(C/C++ で書かれた拡張)を持つ npm パッケージは、事前ビルド済みバイナリが提供されていないと、npm install の時に自動で node-gyp rebuild が走ります。node-gyp はビルド設定ファイル binding.gyp を読み込みます。
この gyp 設定にはコマンド置換構文があり、ビルドの configure フェーズで外部コマンドを実行できます。攻撃者はこれを悪用し、次のような1行を binding.gyp に仕込みます。
'sources': [ "<!(node index.js > /dev/null 2>&1 && echo stub.c)" ]ここで使われている <!(...) がコマンド置換です。ビルド設定を評価する configure フェーズ(コンパイルより前)で node index.js が実行され、標準出力には無害な stub.c というファイル名だけが返るため、ビルドは何事もなく続行します。つまりコンパイルが始まる前に任意の Node.js コードが動くわけです。悪性の binding.gyp はちょうど157バイトだった、と StepSecurity と Snyk の双方が一致して報告しています。
WARNING
ここで最も重要なのは、package.json に preinstall/postinstall/install などのライフサイクルスクリプトが1つも宣言されていない点です。実行のトリガーは binding.gyp 側にあり、npm のスクリプトとしては存在しません。
なぜ既存の監査・スキャナをすり抜けるのか
多くの監査ツールやレビュー観点は「package.json の scripts に怪しいライフサイクルフックが無いか」を見ます。Phantom Gyp はライフサイクルスクリプトを一切宣言しないため、この観点からは不可視です。手口名の「Phantom(幻の)」も、この見えなさに由来します。
一方で npm install --ignore-scripts については、正確に理解しておく必要があります。Snyk によれば、--ignore-scripts は npm の暗黙的な node-gyp rebuild フックもブロックします。したがって「npm 12 やスクリプト無効化では防げない」という言い方は正しくありません。正しい訴求は次の2点です。
- スクリプトを宣言しないため、ライフサイクルスクリプトだけを見る監査から不可視である。
--ignore-scriptsを付けない既定のnpm installで発火する。
つまり、既定のインストール挙動と「スクリプトだけを見る」監査との隙間を突く手口だ、と捉えるのが正確です。
自己増殖のメカニズム: トークン窃取から provenance 偽造まで
Phantom Gyp が実行するコードの目的は、認証情報の窃取と自己増殖です。窃取した npm/GitHub/クラウドのトークンを使い、そのアカウントが公開している全パッケージを列挙し、それぞれに binding.gyp を注入して再公開します。さらに Sigstore を使って SLSA provenance を偽造し、正規のパッケージに見せかける、とされます。
2026年6月3日の主要な波では、単一アカウント jagreehal 配下に汚染が集中し、57パッケージ・286以上の悪性バージョン(Snyk は「数百」と表現)が確認されました。主要な波は6月3日で約2時間未満に集中し、より早い変種は6月1日に出ていた、とされます。影響の大きいパッケージ例として @vapi-ai/server-sdk(月40万ダウンロード超とされる)や ai-sdk-ollama(月12万超とされる)が挙げられています(いずれもセキュリティベンダの報告による値)。
このマルウェアファミリーはMiasmaと名付けられ、以前から観測されている Shai-Hulud ワームファミリーの流れを汲む自己増殖型と説明されています。
盗まれるもの: .npmrc・SSH 鍵・クラウド認証情報・Keychain
窃取の対象は広範です。報告によれば、次のようなものが狙われます。
- クラウド認証情報: AWS アクセスキー・IMDSv2 メタデータ、GCP サービスアカウント、Azure トークン。
- 開発者資格情報: GitHub トークン・GitHub Actions トークン(マスク済みシークレットのメモリ走査を含むとされる)、CI/CD シークレット、SSH 鍵、
~/.npmrc、~/.gitconfig。 - シークレット管理: HashiCorp Vault や Kubernetes のトークン、パスワードマネージャの保管庫(1Password、
pass、gopass)、macOS Keychain、ブラウザのシークレット。
窃取したデータは、攻撃者管理下の GitHub アカウント(多数のリポジトリを暗号化された dead-drop として使う)へ流出する、とされます。.env や .npmrc の扱いは環境変数とシークレット管理、リポジトリからの漏洩パターンはGit シークレット漏洩の事例集も併せて確認しておくと、被害の範囲を具体的にイメージできます。
7月14日の AsyncAPI 波は何が違うか
冒頭で触れた AsyncAPI 汚染は、同じ Miasma ファミリー(Miasma v3)ですが技術が異なります。6月の Phantom Gyp が install 時(node-gyp の configure フェーズ)に発火するのに対し、7月14日のものは require() された時に発火する「require()-time dropper」です。パッケージが読み込まれた瞬間、隠れた detached な Node.js プロセスを起動し、第2段ペイロードをダウンロードする、と説明されています。
Semgrep の調査によれば、2026年7月14日 07:10 UTC、攻撃者は asyncapi/generator への push 権を得て正規のリリースパイプラインを起動し、正規の npm OIDC provenance 付きで公開しました。汚染されたのは次のバージョンです。
@asyncapi/generator@3.3.1@asyncapi/generator-helpers@1.1.1@asyncapi/generator-components@0.7.1@asyncapi/specs@6.11.2(および6.11.2-alpha.1)
この波で狙われたのは、ブラウザのシークレット(Chrome/Brave/Edge)、SSH 鍵、~/.npmrc、~/.gitconfig、AWS/Kubernetes 認証情報、Docker 認証情報、macOS Keychain とされます。ここで注意したいのは、6月の binding.gyp(install 時)と7月14日の AsyncAPI(require() 時ドロッパー)を混同しないことです。同系統ではありますが別技術であり、発火のタイミングも防御の当たり所も変わります。
開発者・CI/CD での現実的な防御とまとめ
Phantom Gyp と Miasma は「provenance があるから安全」「postinstall が無いから安全」という単純な前提を崩します。現実的な防御は、単一の指標に頼らず多層で考えます。
- 依存を固定する:
npm ciでロックファイルを厳密に固定し、意図しないバージョンの混入を防ぎます。SemVer レンジの落とし穴はpackage.json のバージョン指定と SemVerを参照してください。 - スクリプトを既定で切る:
npm install --ignore-scriptsを既定にすると、暗黙の node-gyp rebuild も止まります。ただし副作用として一部のネイティブパッケージがビルドされず動かなくなるため、ビルドが必要なものは隔離環境で明示的にビルドします。 - provenance を検証しつつ過信しない:
npm audit signaturesなどで attestation を検証します。ただし Sigstore の provenance すら偽造され得るため、これ単独の防御にはしません。 - CI を最小権限・短命トークンに: トークンを長期保管せず、egress(外向き通信)を制限します。窃取されても被害範囲と有効期間を絞れます。認証情報窃取に対しては、ハードウェア MFA の併用も有効です。
- ネイティブビルドの有無自体を監査する:
package.jsonにスクリプトが無くても、binding.gypがあればビルドは走ります。依存追加時は「スクリプトの有無」だけでなく「ネイティブビルドが走るかどうか」を審査対象にしてください。
NOTE
Phantom Gyp の教訓は、「監査の観点そのものを攻撃者が読んでいる」という点にあります。スクリプトだけを見る監査、provenance だけを見る検証、いずれも単独では回避され得ます。既定の挙動を安全側へ倒し、権限と通信を絞る多層防御が、結局のところ最も現実的です。
まとめると、2026年夏の npm ワームは、preinstall/postinstall という「わかりやすい入口」を捨て、binding.gyp の暗黙ビルド(6月・install 時)や require() 時ドロッパー(7月14日・AsyncAPI)へと発火点を移しました。同じ Miasma ファミリーでも技術は異なります。既定の npm install で発火し、正規に見える provenance をまとって自己増殖する以上、防御側も単一の指標に頼らない設計が求められます。


