2026年7月14日、AsyncAPIプロジェクトのnpmパッケージ4本・5バージョンにマルウェアが仕込まれて公開された。npmを使う開発者にとってサプライチェーン攻撃はもはや珍しい話ではないが、この事件が既存の類型と決定的に違うのは、入口がnpmトークンの流出ではなくGitHub Actionsの設定不備だった点と、汚染パッケージがプロジェクト本来のリリースパイプラインから、有効な署名(provenance)付きで配られた点にある。しかも実行トリガーは postinstall ではなく require / import だった。

つまり、この数年で「サプライチェーン攻撃対策」として推奨されてきた定番の防御——インストールスクリプトの無効化、来歴証明の確認、公式レジストリからの取得——が、いずれも単独では機能しなかった事例である。本記事では公開されている一次資料とベンダー分析を突き合わせ、時系列・手口・自分の環境での確認手順を、日本語で通しの形に整理する。

2026年7月14日のAsyncAPI侵害タイムライン。05:08の悪性PR作成から11:18の全バージョン削除までを6段で示す
攻撃は2026-07-14 UTC の約6時間で完結した(時刻はMicrosoft Security Blog・StepSecurity・Datadog Security Labsの各分析に基づき本記事で統合)
30秒でわかる AsyncAPI侵害(2026年7月時点)
  • 何が起きた@asyncapi の4パッケージ・5バージョンが悪性コードごとnpmへ公開された(2026-07-14 UTC 07:10〜08:30)。約3時間後にすべて削除済み。
  • 入口pull_request_target でPRのコードをチェックアウトする「pwn request」型のワークフロー。ここからbotアカウントのPATが盗まれた。
  • 厄介な点:悪性版はプロジェクト自身のリリースCIから公開されたため、OIDCによるprovenance署名が有効だった。
  • もう一つの厄介な点:ペイロードはinstall時ではなくimport時に起動する。--ignore-scripts では止まらない。
  • いま確認すること:依存ツリー・lockfile・端末の残留ファイル・自分のリポジトリのワークフローの4点。

この記事では2026年7月のAsyncAPI侵害という個別事件を扱います。攻撃手法の分類や防御ツールの全体像は サプライチェーンセキュリティ2026|攻撃手法・防御ツール・実践チェックリスト をご覧ください。

AsyncAPIサプライチェーン攻撃で何が起きたのか——2026年7月14日の約6時間

AsyncAPIは、イベント駆動型API(Kafka、MQTT、WebSocketなど)の仕様記述フォーマットとそのツール群を提供するOSSプロジェクトである。今回の起点となった asyncapi/generator は、AsyncAPI定義からドキュメントやコードを生成するツールで、GitHubスター1,068・ライセンスはApache-2.0(2026-07-25時点、GitHub APIで取得)。派手なスター数ではないが、@asyncapi/specs のように他のツールから依存として引かれるパッケージを含む点が被害範囲を広げた。

攻撃の流れ

Datadog Security Labsの分析によれば、攻撃者は 05:03〜06:14 UTC にかけて36本のPRを一気に開いている。Wizは37本と数えている。内容のほとんどは偽の寄付ページ追加といった無害を装ったもので、本命のPR #2155 を「大量の通知の中に埋める」ことが目的だったとみられる。

05:08 UTC — PR #2155 を作成(タイトルは docs 追加を装ったもの)
05:11 UTC — ドキュメントプレビュー用ワークフローが起動
05:17 UTC — CI環境から資格情報が外部の貼り付けサービス(Rentry)へ送出される
05:40〜07:05 UTC — 盗んだbot権限でブランチへコミットをpush
06:58 UTCnext ブランチに悪性コミット 3eab3ec が入る
07:10 UTC — プロジェクト本来のリリースCIが3パッケージをnpmへ公開
07:51〜07:56 UTC — 2つ目のリポジトリ asyncapi/spec-json-schemas も侵害される
08:06 / 08:30 UTC@asyncapi/specs の 6.11.2-alpha.1、続いて 6.11.2 が公開
08:49 UTC — 下流環境からの最初のtarball取得をMicrosoftが観測
11:12〜11:18 UTC — 5バージョンすべてがnpmから削除される

StepSecurityの計測では、暴露時間は generator 系3本が約4時間、specs 系が約3時間。短いように見えるが、この間にCIが1回でも回った組織では、該当バージョンがキャッシュとlockfileに残っている可能性がある。

汚染されたのはどれか

パッケージ 悪性バージョン 安全なバージョン 悪性コードの注入先
@asyncapi/specs 6.11.2-alpha.1 / 6.11.2 6.11.1 index.js(ESMビルド出力)
@asyncapi/generator 3.3.1 3.3.0 lib/templates/config/validator.js
@asyncapi/generator-components 0.7.1 0.7.0 lib/utils/ErrorHandling.js
@asyncapi/generator-helpers 1.1.1 1.1.0 src/utils.js

注入先がいずれもそのパッケージが普通に読み込むソースファイルである点に注目したい。新規ファイルの追加でも、package.json のスクリプト欄の書き換えでもない。StepSecurityは、注入コードが1行に圧縮され、行頭に約1,000個の空白を置いてdiff表示から視覚的に押し出す細工がされていたと報告している。

汚染された4パッケージ5バージョンと、週間ダウンロード数の実測値を示すダッシュボード図
週間ダウンロード数は当編集部がnpm registry APIで2026-07-25に取得した実測値(集計期間 2026-07-18〜07-24)。★数・ライセンスはGitHub APIの同日取得値

規模について、事件当日の各ベンダー記事は「合計200万〜300万超/週」と幅のある数字を出している。本記事では自前で数え直した。npm registry APIで2026-07-25に取得した直近1週間(07-18〜07-24)の実測値は、@asyncapi/specs 3,049,266、@asyncapi/generator 142,641、@asyncapi/generator-components 66,871、@asyncapi/generator-helpers 62,565 で、4本の合計は約332万。加えて、汚染されたわけではないが @asyncapi/specs^6.11.1 で参照する @asyncapi/parser が単体で1,088,566ある。被害の大きさを決めたのは generator 本体ではなく、依存として広く引かれる specs 側だったということになる。

現在のレジストリの状態は自分で確認できる

削除済みという事実は、レジストリのメタデータからそのまま読み取れる。npmは公開されたバージョンの公開時刻を time に残したまま、versions からエントリだけを消すため、両者の差分が「かつて存在したが今は取得できないバージョン」になる。

# 6.11.2 は time に残るが versions には無い=unpublish 済みと読める
curl -s https://registry.npmjs.org/@asyncapi/specs | python3 -c "
import json,sys
d = json.load(sys.stdin)
print('versions に存在:', [v for v in d['versions'] if v.startswith('6.11')])
print('time に記録:', {k: v for k, v in d['time'].items() if k.startswith('6.11.2')})
print('latest:', d['dist-tags']['latest'])
"

なお本件にCVE番号は割り当てられていない。「npm 脆弱性」で検索して出てくるライブラリ側の実装欠陥ではなく、正規の公開経路そのものが一時的に乗っ取られたという性質の事案だからだ。バージョン範囲で機械的に判定できる通常の脆弱性対応とは、確認の仕方が変わる。

2026-07-25に実行した結果は次のとおりだった。versions には 6.11.06.11.1 しか残っておらず、time には 6.11.2-alpha.1 が 2026-07-14T08:06:20Z、6.11.2 が同08:30:09Z として記録され、latest6.11.1 に戻っている。@asyncapi/generator も同様に 3.3.x3.3.0 のみだった。レジストリから消えていることと、自分の環境から消えていることは別である点は後述する。

読者の3つの問いへの答え
何が起きた:CIの設定不備を突かれてbotのPATが盗まれ、正規のリリースパイプラインが悪性パッケージを公開した。② 何が問題:署名も出所も正しいまま配られたため、来歴検証だけでは弾けなかった。③ 何をすべき:依存・lockfile・端末の残留物に加えて、自分のリポジトリのワークフローを同じ観点で監査する。

攻撃の入口:GitHub Actionsの「pwn request」がbotのPATを渡した

この事件の技術的な中心は、npmでもマルウェアでもなく、GitHub Actionsのトリガー選択にある。

pull_request_targetとPRのHEADチェックアウトの組み合わせが危険な理由を、通常のpull_requestトリガーと対比した図
「PRで動くCI」でもトリガーによって権限がまったく違う。危険なのはトリガー単体ではなく、PRのコードのチェックアウトとの組み合わせ

なぜ pull_request_target は特別なのか

GitHub Actionsには、PRをきっかけに動くトリガーが2種類ある。通常の pull_requestPR側の文脈で動き、フォークからのPRではsecretsが渡らず、GITHUB_TOKEN も読み取り専用に降格される。対して pull_request_targetベースリポジトリの文脈で動くため、secretsにアクセスでき、書き込み権限も持ちうる。ラベル付けやコメント投稿のように「PRの中身を実行しないが権限は要る」処理のために用意された仕組みである。

危険なのはトリガー単体ではない。pull_request_target で動くジョブが、PRのHEAD(第三者が書いたコード)をチェックアウトしたときに、攻撃者のコードがsecretsのある環境で動く状態が生まれる。この組み合わせは「pwn request」あるいはPoisoned Pipeline Execution(PPE)と呼ばれ、分類としては新しいものではない。当サイトでも手法の側から GitHub Actionsセキュリティ|2026年連鎖攻撃と新ロードマップ完全ガイド で整理している。本記事はその手法が実際に大規模な配布事故へつながった事件の側を扱う。

実際に使われたワークフロー

Datadogは、悪用されたのが manual-netlify-preview.yml(ドキュメントのNetlifyプレビュー用)だと特定している。公開されているファイルを読むと、構造は次のようになっている。

# asyncapi/generator の .github/workflows/manual-netlify-preview.yml(抜粋)
on:
  pull_request_target:          # ベースリポジトリの文脈で動く=secretsが見える
    paths:
      - 'apps/generator/docs/**'

jobs:
  preview:
    steps:
      - name: Checkout generator PR
        uses: actions/checkout@v5
        with:
          ref: ${{ github.event.pull_request.head.sha }}   # 第三者のコードを取得
      # …同じジョブの後段で NETLIFY_AUTH_TOKEN / NETLIFY_SITE_ID / GH_TOKEN を使用

paths フィルタが効いているため、Datadogの表現を借りれば「apps/generator/docs/* 配下にコードを書ける者は、メンテナの承認なしにPRから任意のコードを実行できる」状態だった。実際のPR #2155 は docs 配下へのテンプレート追加を装っている。

CI上で動いた第2段の挙動もDatadogが解析している。/proc/*/environ を走査して INPUT_GITHUB-TOKENNETLIFY_AUTH_TOKENNETLIFY_SITE_ID を拾い、さらに /home/runner/work/_actions/actions/checkout/v5/dist/index.js にパッチを当てて送出処理を仕込み、パイプライン完了後にRentryの貼り付けページへ資格情報を送っていた。同一ジョブ内であれば、環境変数はステップ境界を越えて到達しうるという前提を突いた作りである。

4月には指摘されていた

この構成の危険性は、攻撃の2か月以上前にプロジェクト内部で示されていた。GitHub APIで確認すると、asyncapi/generator の PR #2078「docs: pwn in docs preview poc」は 2026-04-29 に Florence-Njeri 氏によって作成され、2026-05-14 にマージされないままクローズされている。本文には「manual_netlify_preview のGitHub Actionsセキュリティを検証する」旨が書かれており、自動生成のレビュー要約にも「インストール時にリモートのコンテンツを取得して実行するスクリプトを追加する」と記録されている。実証コードとして機能する形で提出されていたわけである。Wizは修正案が5月17日に提示されたが未マージのままだったとし、Chainguardは攻撃の58日前に内部で指摘されていたとしている。

脆弱性は「知られていなかった」のではなく、知られたままレビュー待ちの列に置かれていた

事件後に何が変わり、何が変わっていないか

ここは記事執筆時点(2026-07-25)に本記事が自分で確認した部分である。GitHub APIでコミット履歴を追うと、リリース側は修正されている。PR #2187「ci: limit release workflow to master branch only」が2026-07-16にマージされ、release-with-changesets.yml は現在 master へのpushのみで起動する。攻撃で使われた next ブランチは、現在ブランチ一覧に存在しない。侵害の「出口」だった経路は塞がれた形だ。

一方で、入口だった manual-netlify-preview.yml は、そのパスのコミット履歴上、2026-06-21(2c57fee8、concurrency制御の追加)以降更新されていない。つまりファイルとしては、pull_request_target トリガー、PRのHEADのチェックアウト、同一ジョブ内でのsecrets参照という3点セットが攻撃当時のまま残っている。

ただし、これを「現在も攻撃可能」と読むのは行き過ぎである。理由は2つある。第一に、フォークからのPRに対してワークフロー実行の承認を必須にする設定はリポジトリ側の設定項目であり、APIやファイルからは見えない。第二に、現行ファイルでは npm ciwebsite/ ディレクトリ側で実行されており、PR側のコードが実行に至る経路はファイルを読むだけでは確定できない。確実に言えるのは「危険な組み合わせを含むファイルが、当時と同じ形で残っている」ところまでである。

他人のリポジトリの状態は結論を急がない
ワークフローの安全性は、ファイルの中身とリポジトリ設定(外部コントリビュータの承認要否、環境保護ルール、ブランチ保護)の掛け算で決まる。公開情報だけで「未修正」と断じることはできない。逆に言えば、自分のリポジトリなら両方を確認できる。後半の確認手順はそのためのものだ。

なぜこのサプライチェーン攻撃は既存の防御をすり抜けたのか

この事件が広く分析された理由は、被害規模よりも「効くはずの対策が効かなかった」点にある。ここでは3つに分けて見る。

① 実行トリガーが install ではなく import

postinstall型のマルウェアとimport時実行型のマルウェアで、防御の効き方がどう変わるかを対比した図
インストール時に走る従来型と、モジュール読み込み時に走る今回の型では、止められる場所が違う

npmのサプライチェーン攻撃といえば、長らく postinstall などのライフサイクルフックが定番だった。だからこそ npm install --ignore-scripts が推奨され、npm v12ではデフォルト無効化まで進んだ(この流れ自体は npm v12でpostinstallがデフォルト無効化へ|自システム確認コマンドと段階的な移行手順 で扱った)。

今回のコードは、その前提を外している。Microsoftは、ペイロードが require()import で起動し「一般的な npm install --ignore-scripts による緩和では無効化されない」と明記している。Socketも「実行はパッケージのロード時に起きる。インストールフックは不要」と述べる。起動処理自体は素朴で、spawn('node', [payloadPath], { detached: true, stdio: 'ignore', windowsHide: true }).unref() のように親から切り離した子プロセスを作って自身は何事もなかったように戻る。

この違いは、防御の設計に直接響く。インストール工程だけを監視・制限していると、ビルド時・テスト時・本番プロセスの起動時のいずれでも発火しうる攻撃を見逃す。検知の重心は、プロセス生成と外向き通信の側に移る。

② provenance(来歴証明)が「有効」だった

OIDCによるprovenance署名が証明することと証明しないことを整理した図
provenanceは「どこで作られたか」を証明する仕組みであり、「中身が安全か」は対象外

ここが最も皮肉な部分である。AsyncAPIは2025年11月24日、Shai-Huludキャンペーンで全npmパッケージとVS Code拡張が汚染される被害を受けている。公式ポストモーテムによれば原因は長期間ローテーションされていなかったnpmトークンの流出で、再発防止策として「npmトークンをもう使わない」「OIDCによるTrusted Publisherへ移行する」と表明していた。GitHubのリポジトリ・CIパイプライン・npmレジストリを直接結びつける、より強い方式への移行である。

その移行は実際に行われた。そして今回、攻撃者はトークンを盗む必要がなかった——リポジトリへのpush権限さえ奪えば、プロジェクト本来のCIが正規の手順でパッケージを公開してくれるからだ。StepSecurityは、両リポジトリのリリースワークフローがOIDCのtrusted publisher連携を使っており、悪性バージョンにも有効なSLSA provenance証明が付いていたと報告している。Datadogは、Sigstoreの透明性ログ(Rekor)に「GitHub Workflow Trigger: push」として悪性コミットのSHAが記録されていた一方、対応するGitHub Actionsの実行履歴は事後に手動削除されていたと指摘する。

provenanceが答えるのは「どのリポジトリの、どのワークフローの、どのコミットからビルドされたか」であって、「そのコミットを書いたのが正当な人物か」ではない。署名は移動した信頼の位置を示すだけで、信頼そのものを作らない。前回の教訓に沿って正しく強化した結果、今回は「攻撃の起点がレジストリからリポジトリへ移動した」というのが、この事件の構図である。

③ 侵害されたのがトークンではなく開発フロー

前2者の帰結として、検知側の見え方も変わる。npmトークン窃取型(たとえば npmサプライチェーン攻撃「Mastra AI」|北朝鮮Sapphire Sleetが144パッケージを88分で侵害 のような事例)では、「普段と違う場所からの公開」という異常が残りやすい。今回は公開元も署名もいつもどおりで、異常はもっと手前——PRの洪水、見慣れないフォークからのdocs変更、深夜帯のブランチpush——に現れていた。

観点 トークン窃取型(例:2025年11月のShai-Hulud) 今回のCI侵害型
攻撃の起点 流出した公開用トークン PRから起動するワークフローの設定不備
公開の実行主体 攻撃者の手元 プロジェクトの正規リリースCI
provenance 付かない/不整合が出やすい 有効な署名が付く
実行トリガー postinstall等が主流 require / import 時
主な検知点 レジストリ側の異常公開 リポジトリ側の異常なPR・push
効く対策 トークン廃止・OIDC移行 トリガー設計・ブランチ保護・レビュー体制

何が仕込まれたのか——多段ドロッパーと「Miasma」

実際に配られたコードの構造も、各社の解析でおおむね一致している。第1段は前述のとおりソースファイルに注入された難読化コード(StepSecurityの計測で約7.7KB)で、以降は段階的に本体を取りに行く。

flowchart TD A["アプリ / CI が
@asyncapi/… を require・import"] --> B["Stage 1
ソースに注入された難読化コード
約7.7KB・行頭に大量の空白で隠蔽"] B --> C["detached な node 子プロセスを起動
stdio: ignore / windowsHide"] C --> D["Stage 2
IPFSゲートウェイから sync.js を取得"] D --> E["OS別の“NodeJS”偽ディレクトリへ保存
~/.local/share/NodeJS/sync.js ほか"] E --> F["Stage 3
AES-256-GCMで暗号化された約8.2MBのローダ"] F --> G["Stage 4
Miasma タスキングフレームワーク(約3.1MB)"] G --> H["資格情報の収集
ブラウザ / SSH / npm / クラウド / ウォレット"] G --> I["永続化
systemd・launchd・レジストリRun"] G --> J["C2
HTTP・Nostr・IPFS・BitTorrent DHT・Ethereum"]

投下先はOSごとに決まっており、Linuxなら ~/.local/share/NodeJS/sync.js、macOSなら ~/Library/Application Support/NodeJS/sync.js、Windowsなら %LOCALAPPDATA%\NodeJS\sync.jsNode.jsの正規ディレクトリを装った名前が使われている点が、目視での発見を難しくする。C2はIP 85.137.53.71 のポート8080(コマンド)・8081(アップロード)・8091(プロキシ管理)が主で、加えてNostrリレー、Ethereumのスマートコントラクト、BitTorrent DHT、IPFSといった分散型の予備チャネルが実装されている。単一のドメインをブロックすれば通信が止まる、という前提は成り立たない。

収集対象は、ブラウザ保存のパスワード、SSH秘密鍵、npm・GitHubのトークン、AWS/Azure/GCPの資格情報、Kubernetesのkubeconfig、暗号資産ウォレットなど広範に及ぶ。StepSecurityは130種類以上のファイル形式を対象にしていたと報告している。

ベンダー間で見解が割れている論点

一方で、細部については分析主体によって記述が食い違う。読者が判断を誤らないよう、一致していない点は一致していないものとして並べておく。

論点 見解A 見解B
ワームか否か GMO Flatt Securityは「Miasmaワームフレームワーク」として自己拡散機構に言及 Wizは自己拡散しないと明言し、DirlistGetFilePutFile を持つ古典的なトロイの木馬型のコマンドフレームワークだと分類。Chainguardも自己増殖はしないとする
AIコーディング支援ツールの標的化 StepSecurityは ai-tool-poisoner.js がClaude Code・GitHub Copilot・Cursorを狙うと報告。Datadogも .vscode/tasks.json.claude/settings.json への注入に言及 Socketは「Claude Code・Cursor・Copilot・IDE設定ファイルを特定して狙う証拠は見つからなかった」と明記
攻撃者の帰属 第3段コードが自ら M-RED-TEAM v6.4 と名乗り、キャンペーンIDは miasma-train-p1 Wizは「現時点で確定的な帰属は行わない」とし、複数グループの特徴が混在すると述べる
ペイロードのサイズ 暗号化ローダ 約8.2〜8.25MB 最終フレームワーク 約3.08〜3.09MB(Socketは744モジュールと計測)。両者は別段のもので矛盾ではない

AIコーディング支援ツールが標的に含まれるかは、当サイトの読者にとって最も気になる点だろう。だが現時点で断定はできない。複数のベンダーが該当モジュールの存在を報告する一方、明示的に否定する分析もある。実務上は「該当版を読み込んだ端末では、露出期間中にAI支援で書かれたコードや設定も点検対象に含める」という保守的な扱いが妥当だ。IDEの設定ファイルやエージェント設定の改ざん自体は、当サイトで扱ったGhostApproval・SymJackのシンボリックリンク経由の承認ハイジャックなど、別の事例でも繰り返し観測されている攻撃面ではある。

自分の環境を確認する——依存・lockfile・痕跡・自前CIの4点

ここからが実務である。レジストリから削除されたことは、自分の環境が安全になったことを意味しない。tarballのキャッシュ、node_modules、lockfileの固定、ビルド済みイメージのいずれにも残りうる。

依存・lockfile・痕跡・自前CI・鍵の5つの確認ポイントを並べた図
確認は「入っているか」だけで終わらない。実行痕跡と、自分のCIの構成まで見る

ステップ1:依存ツリーとlockfileを見る

# 1) 現在の依存ツリーに該当パッケージが居るか(transitive も含めて表示)
npm ls @asyncapi/specs @asyncapi/generator @asyncapi/generator-helpers @asyncapi/generator-components --all 2>/dev/null

# 2) lockfile に悪性バージョンが固定されていないか(yarn/pnpm も同時に走査)
grep -rnE "@asyncapi/(specs|generator|generator-helpers|generator-components)" \
  --include="package-lock.json" --include="yarn.lock" --include="pnpm-lock.yaml" . \
  | grep -E "6\.11\.2|3\.3\.1|1\.1\.1|0\.7\.1"

# 3) ローカルのnpmキャッシュに該当tarballが残っていないか
npm cache ls 2>/dev/null | grep -E "asyncapi.*(6\.11\.2|3\.3\.1|1\.1\.1|0\.7\.1)" || echo "キャッシュに該当なし"

該当が出た場合、npm install を再実行すれば悪性版はもう解決されない(削除済みのため)。lockfileを作り直し、依存経由で拾ってしまう場合は overrides で安全版に固定する。

{
  "overrides": {
    "@asyncapi/specs": "6.11.1"
  }
}

ステップ2:実行痕跡を探す

インストールの有無ではなく、読み込まれて起動したかを見る。

# 投下ファイル(Node.jsの正規ディレクトリを装った名前)
ls -la ~/.local/share/NodeJS/sync.js ~/Library/Application\ Support/NodeJS/sync.js ~/.config/NodeJS/sync.js 2>/dev/null

# 永続化(Linux: systemd ユーザーユニット / macOS: シェルRCへの追記)
systemctl --user list-units 2>/dev/null | grep -i miasma
grep -n "NodeJS/sync.js\|miasma" ~/.zshrc ~/.bashrc ~/.bash_profile 2>/dev/null

# 見覚えのない node プロセス(親から切り離されているため孤児化しやすい)
ps -eo pid,ppid,etime,command 2>/dev/null | grep "[N]odeJS"

Windowsでは %LOCALAPPDATA%\NodeJS\sync.js の存在と、HKCU の Run キーに miasma-monitor が登録されていないかを確認する。ネットワーク側では、85.137.53.71(8080/8081/8091)への通信と、ビルド中に発生したIPFSゲートウェイ(ipfs.io など)へのアクセスをログから探す。

ステップ3:自分のリポジトリのワークフローを監査する

ここが今回の事件から得られる最大の持ち帰りである。 対象は「AsyncAPIを使っているか」ではなく「同じ形のワークフローを持っていないか」だ。単に pull_request_target を検索するだけでは、ラベル付けのような安全な用途まで拾ってしまう。危険なのは組み合わせなので、同一ファイル内でPRのHEADをチェックアウトしているかまで見る。

# pull_request_target と「PRのHEADのチェックアウト」が同居するワークフローだけを挙げる
for f in $(find . -path "*/.github/workflows/*.y*ml" 2>/dev/null); do
  if grep -q "pull_request_target" "$f" && \
     grep -qE 'ref:[[:space:]]*\$\{\{[[:space:]]*github\.event\.pull_request\.head\.(sha|ref)' "$f"; then
    echo "⚠ 要確認: $f"
    grep -nE "pull_request_target|head\.(sha|ref)|secrets\." "$f"
  fi
done

出力があったワークフローについては、次の順で潰す。
そのジョブは本当にPRのコードを読む必要があるか。プレビュー生成のようにビルドが要るなら、権限のあるジョブと分離する
secretsを同じジョブに置いていないか。参照するステップを別ジョブ・別環境(Environment保護ルール付き)へ移す
外部コントリビュータのワークフロー実行に承認を必須にしているか。リポジトリ設定側で締める
サードパーティActionをSHAで固定しているか。タグは上書きされうる

ステップ4:露出していた場合の鍵の扱い

該当版を実際に読み込んだ形跡がある端末・CIランナーは、資格情報が抜けた前提で扱う。順序は「侵害された端末以外のクリーンな環境から」「影響の大きいものから」が原則になる。npmトークン → GitHubのPAT・デプロイキー → SSH秘密鍵 → クラウド資格情報 → ブラウザ保存のパスワードとCookie、という並びが各社の推奨に共通している。投下ファイルとプロセスを先に除去してから再発行しないと、新しい鍵をその場で盗まれる点に注意したい。

影響範囲と対応の優先順位

すべての読者が同じ対応を要するわけではない。立場ごとに整理する。

状況 リスク 最優先でやること
該当版を require / import した端末・CIがある 痕跡の除去 → 資格情報の全面再発行 → CIログの外向き通信の確認
lockfileに該当版はあるが、インストール・実行の記録がない lockfile再生成と overrides 固定、キャッシュ削除、念のためのトークン再発行
@asyncapi/parser など依存経由でspecsを引いている 解決されたバージョンを npm ls で実測し、6.11.2系でないことを確認
AsyncAPIは未使用だが、pull_request_target を使うリポジトリがある ステップ3の監査。今回の事件の再現条件は自分側にある
いずれにも該当しない 事例として「provenanceは中身を保証しない」という原則だけ持ち帰る

期間の目安も押さえておきたい。悪性版が公開されたのは UTC 07:10〜08:30、日本時間では 2026年7月14日 16:10〜17:30 にあたる。削除が完了したのは UTC 11:12〜11:18、日本時間で同日 20:12〜20:18 である。つまり取得できてしまう可能性があったのは、日本時間で7月14日の16:10から20:18まで。この時間帯にCIが回った、あるいは依存を更新した記憶があるなら、優先度を一段上げてよい。

「削除済み」で安心しない
npmから消えているのは新規取得の話であり、すでにpullされたDockerイメージ、CIのキャッシュ、開発者端末の node_modules には残る。イメージを固定タグで運用している場合は、該当時間帯に焼いたレイヤーがないかを確認する。

開発フロー側の恒久対策

今回の事件は、依存管理よりもリポジトリ運用の問題として読むほうが再発防止に直結する。
危険なトリガーの棚卸しpull_request_target × PRコードのチェックアウトを禁止パターンとして扱い、静的解析(zizmorなど)をCIに常設する
リリース経路の最小化:公開の起点となるブランチを限定する(AsyncAPIがPR #2187で行ったのがまさにこれ)
指摘の滞留を作らない:セキュリティ指摘のPR・Issueには他と別のSLAを与える。今回は実証コード付きの指摘が2か月以上滞留した
大量PRを異常として扱う:短時間に開かれた大量のPRは、レビュー疲れを狙ったノイズかもしれない
import時実行を前提にした検知:インストール工程だけでなく、ビルド・テスト・起動時のプロセス生成と外向き通信を監視対象に含める

まとめ

AsyncAPI侵害から持ち帰るべき5点
① 汚染は @asyncapi の4パッケージ・5バージョン(generator 3.3.1 / components 0.7.1 / helpers 1.1.1 / specs 6.11.2-alpha.1・6.11.2)。安全版は 3.3.0 / 0.7.0 / 1.1.0 / 6.11.1。
② 入口は pull_request_target でPRのコードをチェックアウトするpwn request型のワークフロー。指摘は約2か月前から存在した。
③ 公開したのはプロジェクト自身のCI。OIDCによるprovenanceは有効で、来歴検証では弾けない。
④ ペイロードはimport時に起動する。--ignore-scripts やpostinstall無効化では止まらない。
⑤ 自分の確認は「依存・lockfile・痕跡」に加えて自分のワークフローまで。再現条件は自分側にある。

サプライチェーン攻撃の対策は、レジストリとの境界を固めるフェーズから、開発フローそのものを攻撃面として扱うフェーズへ移りつつある。AsyncAPIは2025年11月の被害を受けて公開経路を強化し、その方向自体は正しかった。それでも、コードがリポジトリに入る手前の経路が緩ければ、強化された公開経路は「信頼できる配送路」として攻撃者に利用される。今回の事件が示したのはその一点に尽きる。

まずは手元で npm ls と lockfileのgrepを実行し、続けて自分のリポジトリで pull_request_target の監査コマンドを走らせてほしい。前者は5分、後者は10分で終わる。

参照ソース

Microsoft Security Blog — Unpacking the AsyncAPI npm supply chain compromise and import-time payload delivery — 分単位のタイムライン、注入ファイル、import時実行の挙動、IoC
StepSecurity — Coordinated AsyncAPI Supply Chain Attack: Miasma RAT Delivered via Compromised CI/CD Pipelines in Two Repositories — 2リポジトリ侵害の経緯、暴露時間、OIDC provenance が有効だった点
Datadog Security Labs — Compromised AsyncAPI npm packages: inside a CI supply-chain attack — 悪用ワークフローの特定、CI上での資格情報窃取の解析、Rekorログの記録
Wiz — M-Red-Team: AsyncAPI Supply Chain Compromise via GitHub Actions — 自己拡散しない旨の分類、帰属を確定しない立場、4月からの指摘の経緯
Chainguard — AsyncAPI supply chain compromise: npm packages backdoored via GitHub Actions “pwn request” — pwn requestの分類、安全版の一覧、緩和の順序
Socket — Compromised npm Packages in the AsyncAPI Namespace Deliver Multi-Stage Botnet Loader — 多段ドロッパーの構造とサイズ、AIツール標的化を確認できないとする記述
AsyncAPI Initiative — Shai-Hulud: What Happened, How We Fixed It, and What We Learned — 2025年11月の侵害に対する公式ポストモーテムとTrusted Publisher移行の表明
asyncapi/generator PR #2078「docs: pwn in docs preview poc」 — 2026-04-29作成・05-14に未マージでクローズされた実証PR(本記事がGitHub APIで確認)
asyncapi/generator PR #2187「ci: limit release workflow to master branch only」 — 2026-07-16にマージされたリリース経路の限定
GMO Flatt Security Blog — AsyncAPI ソフトウェアサプライチェーン攻撃の概要と対応指針 — 日本語の既報。多段ペイロードの解析とIoC・除去手順を扱う