2026年6月3日、npmの57パッケージ・286を超えるバージョンが、わずか2時間足らずの間に一斉に汚染された。攻撃者はpreinstallpostinstallといった、セキュリティツールが監視するlifecycle scriptを一切使っていない。代わりに、ネイティブ拡張のビルド設定ファイルbinding.gypを武器化し、npm installの最中にnode-gypを経由してコードを実行させた。StepSecurityはこの手口を「Phantom Gyp」と命名し、背後のマルウェアファミリを自己増殖ワーム「Miasma」として追跡している。

npmサプライチェーン攻撃全体の防御フレームワークと恒久対策についてはサプライチェーンセキュリティ完全ガイド2026|攻撃手法・防御ツール・実践チェックリストをご覧ください。

本記事は攻撃の武器化コードそのものは掲載しない。各ベンダーが公開済みの「仕組み」と「検知パターン」に絞って、自衛のために必要な情報を整理する。

30秒で理解する

この記事のポイント
  • ・2026-06-03、npm 57パッケージ・286超バージョンが約2時間で汚染。最大の被害は週40万DL超の@vapi-ai/server-sdk
  • preinstallを使わず、binding.gypのコマンド置換構文でnode-gypのビルドを誘発してインストール時に実行する「Phantom Gyp」手法。
  • --ignore-scriptsでは止まらない。lifecycle script監視を意図的に回避している。
  • ・ROT暗号→AES-128-GCM→Bunランタイム逃避の多段ローダーで、npm/GitHub/AWS/GCP/Azure/Vault/K8sの認証情報を窃取。GitHub Actionsのランナーメモリも走査する。
  • .claude/.cursor/.gemini/に設定ファイルを注入し、AIコーディングアシスタントを再実行トリガーにする新手口。パッケージ削除後も復活し得る。
  • ・該当版を入れたなら侵害前提。全シークレットを即ローテし、注入ファイルを除去、当該版をブロックする。

Phantom Gypとは何か――binding.gypを悪用するnpmワーム

「Phantom Gyp」は脆弱性の名前ではなく、攻撃手法(テクニック)の名前だ。StepSecurityが、ほとんどのセキュリティツールが覗かないファイル――binding.gyp――に攻撃が潜む点を捉えて、この名を付けた。

まず用語を整理しておく。3つの名前が同じ事件をめぐって飛び交っているからだ。

Phantom Gypbinding.gypを使ってインストール時にコードを走らせる「実行手法」の呼称(StepSecurityによる命名)
Miasma=この手法を使う「マルウェアファミリ」の呼称。Wiz Researchが6月初旬の前哨戦時に命名した
Shai-Hulud=2025年来続く源流の「ワーム系統」。今回はその子孫にあたる

攻撃者は明確に解析側を意識している。認証情報の窃取先となるGitHubアカウントliuende501には236個のリポジトリがプログラム的に量産され、その一部は逆さ文字で『Shai-Hulud: Here We Go Again(また始まったぞ)』と読めるよう命名されていた。StepSecurityの過去解析を名指しで挑発する形だ。

今回の波は、6月初旬から続くMiasma活動の延長線上にある。6月1日には@redhat-cloud-servicesスコープの31パッケージが侵害され、Microsoft Threat Intelligenceが警告を発した(当サイトの解説記事)。その時点ではpreinstallフックでBunを取得する従来型だった。Phantom Gypはそこから初期実行をlifecycle scriptからビルド時ファイルへ移した点が決定的に新しい。

なぜbinding.gypが盲点になるのか

binding.gypは、C/C++で書かれたネイティブアドオンをビルドするための設定ファイルだ。npmはパッケージにこのファイルがあると、npm installの際にnode-gyp rebuildを自動的に呼び出す。これは正規のnpmの挙動であり、何も不正ではない。攻撃者はこの「正規の自動ビルド」に便乗した。

肝は、binding.gyplifecycle scriptとして分類されない点にある。多くのサプライチェーン防御はpackage.jsonpreinstall/postinstall/installといったスクリプトフィールドを監視する。binding.gypはそこに現れない。だから、package.jsonだけを見ているツールには「何も怪しいものがない」ように映る。Snykはこれを「binding.gypはlifecycle scriptとして分類されないため、スクリプト中心の防御を回避する」と説明している。

GYP(Generate Your Projects)の設定構文には、ビルド設定の段階でシェルコマンドを実行し、その標準出力を値として埋め込む「コマンド置換」の機能がある。記法はソース指定の中に<!(...)を書く形だ。攻撃者はこの正規機能を使い、ソースファイル名を返すふりをしながら裏でスクリプトを起動し、ビルドがエラーにならないようダミーのファイル名を返していた。コンパイルが始まる前の「設定フェーズ」で実行が走るため、ネイティブのコンパイル監視よりさらに手前で発火する。

ここで重要な誤解を1つ潰しておく。「npm install --ignore-scriptsを付けておけば安全では?」という反応だ。これは効かない--ignore-scriptspackage.jsonのlifecycle scriptを止めるだけで、binding.gypを起点とするnode-gypのビルドは止めない。Snykは「--ignore-scriptsはlifecycle scriptをブロックするが、binding.gypが誘発するnode-gypビルドは止めない」と明言している。Phantom Gypがわざわざ実行点をビルド時ファイルへ移したのは、まさにこの広く知られた緩和策を無効化するためだ。

検知に使える発想
「ネイティブ拡張がないはずのパッケージで node-gyp rebuild が走る」「binding.gyp<!( が含まれる」「インストール中に buncurl の子プロセスが生える」――この3点はすべてのベンダーが共通して挙げる異常シグナルだ。後述のワンライナーで自環境を確認できる。

攻撃の全体像――4段ローダーとBunランタイム逃避

binding.gypが起動するのは、ルート直下に置かれた肥大化したindex.js(4.5MB前後)だ。これは単なるスクリプトではなく、検知を順に剥がしていく多段ローダーになっている。StepSecurityとSnykのリバースエンジニアリングによれば、構造はおおむね次の4段だ。

flowchart TD A["npm install 実行"] --> B["binding.gyp を検出
node-gyp rebuild を自動起動"] B --> C["コマンド置換構文で
ルートの index.js を実行"] C --> D["Stage1: ROT-N カエサル暗号
eval で復号(ROT-9/15/18/19/20 を観測)"] D --> E["Stage2: AES-128-GCM 復号
鍵・IV・認証タグを内包"] E --> F["Stage3: Bun v1.3.13 を取得
Node.js の外で実行=監視逃避"] F --> G["Stage4: 本体ペイロード
難読化された認証情報スティーラー"] G --> H["認証情報を窃取・GitHub経由で外部送出"] G --> I["AIアシスタント設定を注入し
再実行トリガーを残置"]

各段の役割を言葉で補う。

Stage1(ROT-N):ファイル全体を単純なカエサル暗号で包み、evalで復号する。パッケージごとに回転値を変える(ROT-9/15/18/19/20など)ことで、単一シグネチャでの検知を避けている
Stage2(AES-128-GCM):鍵・初期化ベクトル・認証タグを埋め込んだ正攻法の対称暗号で、本体を復号する
Stage3(Bunランタイム逃避):公式GitHubリリースからBun v1.3.13を取得し、Node.jsプロセスの外で最終段を動かす。Node.jsの子プロセス監視やEDRのシグネチャを回避するための逃避だ
Stage4(本体):難読化された認証情報スティーラー。obfuscator.ioで難読化され、暗号化された文字列テーブルを持つ

Bunは攻撃対象ではないし、Bun自体に脆弱性があるわけでもない。ここでBunは「Node.js監視の死角で動かすための実行環境」として悪用されているにすぎない。前哨戦のMiasmaがoven-sh/setup-bunを使ったのと同じ発想を、ビルド時実行と組み合わせて先鋭化させた形だ。

何が盗まれるのか――認証情報の窃取とランナーメモリ走査

本体ペイロードは包括的な認証情報ハーベスタとして動く。狙うのは開発者ローカルとCI/CDの双方だ。各社の解析を突き合わせると、対象は以下に及ぶ。

クラウド:AWS(アクセスキー、IMDSトークン)、GCP(GOOGLE_APPLICATION_CREDENTIALS、サービスアカウント鍵)、Azure(マネージドID、Key Vault)
シークレット管理:HashiCorp Vault、Kubernetesサービスアカウントトークン
レジストリ/VCS:npmトークン、GitHub(PAT、Actionsトークン)、RubyGems
パスワードマネージャ:1Password、passgopass
その他:SSH鍵、Slackトークン

特に厄介なのがGitHub Actionsランナーのメモリ走査だ。GitHub Actionsは通常、ログ上でシークレットをマスク(伏せ字)する。ところがこのマルウェアはランナーのプロセスメモリを直接走査し、マスクされる前の生の値を引き抜く。Snykはこれを「ランナーのプロセスメモリを走査して、マスクされたシークレットをマスクされていない形で取り出す」と表現している。つまりCIのログを伏せ字にしているだけでは守れない。

窃取した情報は、攻撃者が量産したGitHubリポジトリ群(liuende501配下、結果ファイルとしてresults/にJSONで投入)へ、認証済みのGitHub API呼び出しで送出される。外部C2サーバへの怪しい通信ではなく、GitHubという正規サービス内で完結するため、 egress監視でも見つけにくい。

そして窃取だけでは終わらない。このワームは自己増殖する。npmではメンテナのパッケージ一覧をレジストリAPIで列挙し、各パッケージに悪性ファイルを注入して再publishする。RubyGemsでは同等のロジックをextconf.rb(RubyGemsのネイティブ拡張フック)に仕込む。手の届くメンテナアカウントから次々と版を撒き直すことで、汚染が連鎖的に広がる構造だ。

AIコーディングアシスタントを再実行トリガーにする新手口

今回の波で最も新しいのが、AIコーディングアシスタントを「再実行のトリガー」に仕立てる手口だ。マルウェアはリポジトリに、IDEやAIエージェントがフォルダを開いたときに自動実行される設定ファイルを注入する。各社の解析が一致して挙げる注入先は次のとおりだ。

注入先ファイル対象ツール発火タイミング
.claude/settings.json / .claude/setup.mjsClaude Code(Anthropic)プロジェクトを開く/フック起動時
.cursor/rules/setup.mdcCursor AIルール読み込み時
.gemini/settings.jsonGoogle Gemini設定読み込み時
.vscode/tasks.jsonrunOn: folderOpenVS Codeフォルダを開いた瞬間
.github/setup.jsGitHub Actionsワークフロー実行時

これらはGraphQLミューテーション経由でコミットされ、「これはIDE連携と依存関係セットアップに必要です」という社会工学的なメッセージが添えられていた。

何が怖いのか。パッケージを削除しても侵害が終わらない点だ。npm uninstallで悪性パッケージを消しても、リポジトリに残った.claude/.cursor/の設定が生き残る。開発者が次にそのプロジェクトをAI支援IDEで開いた瞬間、バックドアが再発火する。さらにAIエージェントが生成するコードに攻撃者の指示を紛れ込ませる「汚染」の経路にもなり得る。AIコーディングが日常になった開発フローそのものが、永続化の足場として狙われ始めたということだ。

対処の順序に注意
感染が疑われる場合、認証情報のローテーションより先に、まず注入された永続化フック(.claude/.cursor/.vscode/等)を除去する。フックを残したまま鍵だけ回しても、IDEを開いた瞬間に新しい鍵が再び盗まれる。Snykも「永続化フックの除去を認証情報ローテーションの前に」と順序を強調している。

影響パッケージと規模――57パッケージ・286バージョン

被害は57パッケージ・286を超える悪性バージョンに及ぶ。代表的なものを規模感とともに整理する。ダウンロード数は各社解析で集計時点が異なるため幅がある点に注意してほしい。

パッケージ性質規模の目安確認された悪性版(例)
@vapi-ai/server-sdkVapi.ai 音声AIの公式サーバSDK月40万DL超0.11.1 / 0.11.2 / 1.2.1 / 1.2.2
ai-sdk-ollamaOllama連携の AI SDK月12万DL前後0.13.1 / 1.1.1 / 2.2.1 / 3.8.5
autotel ファミリテレメトリ系(25〜30+サブパッケージ)メンテナjagreehal配下100超バージョン
awaitly ファミリ非同期ユーティリティ(6パッケージ)80超バージョン
executable-stories ファミリドキュメント系(8パッケージ)50超バージョン
wrangler-deploy / node-env-resolver / mountly ほか各種ツール個別版

特徴は、単一の有名パッケージを狙うのではなく、メンテナアカウント単位で配下を一網打尽にしている点だ。autotelファミリは単一メンテナjagreehalの配下にまとめて注入されており、自己増殖の「メンテナ列挙→一斉再publish」が実際に機能したことがうかがえる。

ここで実務上の最大の落とし穴が「latestタグの罠」だ。攻撃者はlatestを正規版へ戻しつつ、過去の悪性バージョンをnpm上に残置している。Snykは「クリーンなlatestタグを安全の証拠と見なすな」と警告する。lockファイルで特定版を固定していたり、^~の範囲指定で古い版を引いたりすると、latestが綺麗でも侵害版が入り込む。判断はタグではなく、lockファイルとインストール済みの実体で行う必要がある。

自分の環境が感染しているか確認する

まず、自プロジェクトが影響パッケージに依存していないかをlockファイルで確認する。

# lockファイルで影響パッケージ名を横断検索
grep -nE '@vapi-ai/server-sdk|ai-sdk-ollama|autotel|awaitly|executable-stories|wrangler-deploy|node-env-resolver' \
  package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null

次に、インストール済みのnode_modulesを直接調べる。Phantom Gypの核心はbinding.gyp内のコマンド置換なので、各社が共通して挙げる検知ワンライナーがそのまま使える。

# binding.gyp にコマンド置換構文 <!( が含まれるパッケージを洗い出す
grep -rl '<!(' node_modules/*/binding.gyp 2>/dev/null

# ネイティブ拡張がないはずなのに巨大なルート index.js を持つパッケージを探す
find node_modules -maxdepth 2 -name index.js -size +1M 2>/dev/null

最後に、リポジトリに永続化フックが注入されていないかを確認する。これはnpm uninstallでは消えないため、必ず別途チェックする。

# AIアシスタント/IDE 向けに注入される永続化フックを確認
ls -la .claude/ .cursor/ .gemini/ .vscode/tasks.json .github/setup.js 2>/dev/null
grep -RInE 'folderOpen|setup\.mjs|IDE integration' .vscode .claude .cursor 2>/dev/null

これらに心当たりのない出力があれば、侵害を前提に次節の対処へ進む。なおbunバイナリが/tmp/b-*のような一時ディレクトリに落ちている形跡、インストール中にnode-gyp rebuildがネイティブ拡張なしのパッケージで走った記録も、StepSecurityが挙げる挙動シグナルだ。

緊急対処と恒久防御

侵害版を導入したと判明したら、インストール=コード実行が起きた前提で動く。各社解析の最大公約数を、順序つきで示す。

緊急対処(この順序で)
  • ・①永続化フックの除去.claude/.cursor/.gemini/.vscode/tasks.json.github/setup.jsの注入ファイルを先に消す。
  • ・②全認証情報のローテーション:npm・GitHub(PAT/Actions)・AWS・GCP・Azure・Vault・K8s・RubyGems・SSH鍵。クリーンな端末から実施する。
  • ・③クリーン再インストール:侵害版を信頼できる旧版にピン留めし、node_modulesを削除して入れ直す。
  • ・④GitHub監査:見覚えのない公開リポジトリ・注入ワークフロー・自動化トークンの不審な活動を確認する。
  • ・⑤当該版のブロック:286版をブロックリスト/レジストリポリシーで遮断する。

恒久防御は、今回の手口が「lifecycle script監視を回避した」ことを正面から受け止めた設計にする。下表に、よく挙がる対策と今回への有効性を整理した。

対策Phantom Gypへの有効性補足
--ignore-scripts の既定化△ 限定的lifecycle scriptは止まるが、binding.gyp由来のnode-gypビルドは止まらない
ビルドステップの隔離・遮断
(ビルド不要なら無効化)
◎ 有効node-gyp実行自体を封じれば発火点を断てる。Chainguardはソースからビルドし当該経路を排除
レジストリのcooldownポリシー○ 有効publish直後の版を一定時間引かない。2時間で撒く速攻型に効く
publishトークンの短命化・MFA・承認ゲート○ 有効メンテナ乗っ取りからの再publishの起点を塞ぐ
SLSA provenance(来歴署名)△ 限定的「誰がpublishしたか」は保証するが「中身が安全か」は保証しない。正規ルート乗っ取りには無力
lockファイル整合性検証+実体スキャン○ 有効latestタグでなく実体で判断。binding.gyp/巨大index.jsを検査

要点は、署名(誰が)だけでなく中身(何を)を検証すること、そしてビルド時の実行経路を最小化することの2つだ。Chainguardが今回無傷だったのは、上流のレジストリtarballを消費せず自社ファクトリでソースからビルドし、lifecycle scriptもnode-gyp呼び出しも実行しない構成だったためで、設計でビルド時実行経路を断った好例といえる。トークン窃取そのものへの多層防御は、トークン窃取を多層で止める防御パターンも併せて参照してほしい。

まとめ――「監視されていない場所」を突く攻撃の次の一手

Phantom Gypが示したのは、サプライチェーン攻撃が「監視されている場所」を避け、「監視されていない場所」へ実行点を移し続けるという力学だ。preinstallが監視されればbinding.gypへ、Node.jsプロセスが監視されればBunへ、パッケージが消されればAIアシスタントの設定ファイルへ。防御側が一手指すたびに、攻撃側はまだ誰も見ていない隙間へ移動する。

だからこそ、特定の手口名(Phantom Gyp)を覚えるだけでは足りない。「インストール=信頼できないコードの実行になり得る」という前提に立ち、ビルド時実行の隔離、実体ベースの検査、シークレットの短命化と中身検証という構造的な防御へ寄せていくことが、次の名前が付く前の備えになる。npmサプライチェーン全体の体系的な防御はサプライチェーンセキュリティ完全ガイド2026に、AIコーディング環境を狙う攻撃の広がりはClaudeユーザーデータを狙うnpmマルウェア解説にまとめている。

参照ソース

・StepSecurity「Miasma Worm: Phantom Gyp Supply Chain Attack」 — 手法命名と詳細リバースエンジニアリング(一次解析): https://www.stepsecurity.io/blog/binding-gyp-npm-supply-chain-attack-spreads-like-worm
・Snyk「Node-gyp Supply Chain Compromise」 — 自己増殖機構と--ignore-scriptsが効かない理由の解説: https://snyk.io/blog/node-gyp-supply-chain-compromise-self-propagating-npm-worm-binding-gyp/
・Chainguard「Miasma Phantom Gyp npm attack」 — 規模(57/286)とビルド経路遮断による防御: https://www.chainguard.dev/unchained/chainguard-artifacts-safe-from-miasma-phantom-gyp-npm-attack
・Corgea Research「Miasma Phantom Gyp npm worm」 — 影響パッケージ一覧と版情報: https://corgea.com/research/miasma-phantom-gyp-npm-worm-vapi-ai-sdk-ollama-june-2026
・The Hacker News「IronWorm and New Miasma Worm Variant Hit npm」 — 系統と全体動向: https://thehackernews.com/2026/06/ironworm-and-new-miasma-worm-variant.html