エージェント型ワークフローインジェクション(CVE-2026-44246)に関する再現可能な脆弱および修正済みのGitHub Actionsフィクスチャ。測定された検出器カバレッジと緩和ガイダンス付き。
CVE-2026-44246 (nnU-Net、エージェント型ワークフローインジェクション、CVSS 7.2、CWE-1427) として公開されたバグ分類に対する、再現可能な脆弱版/修正版フィクスチャと、それらに対する3つの検出器の実測出力。
これは、検出器をテストする対象が何もなかったために存在する。この分類のルールを書くということは、フィクスチャを手で書き、それが忠実であることを願うということであり、公開されたリビジョンはどのコミットを見るべきかを知っている場合にのみ入手可能だった——そしてそこが結局のところ興味深い部分だった。
GitHub Actions ワークフローが、リポジトリの認証情報を持つ AI エージェントに信頼できないテキストを渡す。
4つの条件があり、すべてが必要である:
issues、issue_comment、pull_request_review —— GitHub アカウントを持つ誰もがテキストを作成できるイベント。issues: write、contents: write など。claude-code-action と codex-action は、入力 (、) が明示的にオプトインしない限り、書き込み権限のない実行アクターを拒否する。その入力がなければ、信頼できない作成者がエージェントに到達することはなく、そのワークフローはこの構成ではなく安全な構成である。allowed_non_write_usersallow-usersprompt: 内の ${{ github.event.issue.body }})、ジョブが渡すトークンを使って実行時に取得されるか (gh issue view)。これはテンプレートインジェクションではない。シェルとして評価されるものは何もない。ペイロードは散文であり、インタプリタはモデルである。だからこそ、いつものアドバイス——変数をクォートせよ、eval するな——は当てはまらず、以下の修正が入力のエスケープではなく、エージェントが何に到達できるかに関するものになっている。
nnU-Net はこのワークフローを2つのコミットで強化しており、「前」と「後」を二値として扱う検出器は真ん中のものを誤る。
| リビジョン | コミット | 日付 | エージェントの --allowedTools | 判定 |
|---|---|---|---|---|
| 脆弱 | 94300b49e716 | 2026-04-13 | gh issue comment、gh issue edit | 到達可能な書き込み |
| 「修正」 | 4e4770b0b0e6 | 2026-04-24 | gh issue comment のみ; ラベリングはラッパースクリプトに移動 | 依然として到達可能な書き込み |
| 後続 | 11bd8746fc06 | 2026-04-27 | どちらもなし; 後続のステップがエージェントの書いたファイルから投稿 | 到達不能 |
誰もが修正と呼ぶであろうコミット——そのメッセージは*「hardened issue and PR agents」*——は gh issue edit を削除し、ラベルを .github/scripts/safe-label.sh 経由にしたが、Bash(gh issue comment:*) をエージェントに残した。エージェントは依然としてコメントするよう誘導される可能性があり、許可リストパターン gh issue comment:* はトリガーとなった issue にスコープされていないため、ターゲットはモデルが選ぶままだった。これを閉じたのは3番目のコミットだけである: エージェントは /tmp/issue-comment.md を書き、後続の非エージェントステップがイベントから取得した ISSUE: ${{ github.event.issue.number }} を使ってそれを投稿する。
そこから2つのことが導かれ、それがこのリポジトリが単なる2つのファイルではない理由である:
「修正済み」はバージョンではなくリビジョンに関する主張である。 v2.4.1 が修正リリースとして引用されているが、その .github/workflows/ には codespell.yml しか含まれていない——エージェントワークフローはそのタグには全く存在しない。タグから修正を検証することはできない; コミットをピン留めする必要がある。
permissions ブロックは決して変わらない。 issues: write は3つのリビジョンすべてに存在し正しく、最後のものも含めて、後続のステップがそれを必要とするためである。permissions だけに注目する検出器は、リビジョン1とリビジョン3を区別できない。それらを区別するのは、エージェントが何を呼び出せるかである。
3つの検出器を、両方のフィクスチャと3つの実際のリビジョンすべてに対して実行した。完全な生出力とツールのバージョンは results.md にある。
| リビジョン | agentbound 0.1.3 | sisakulint v0.3.7 | zizmor 1.30.1 |
|---|---|---|---|
1-vulnerable | HIGH write-scope、HIGH untrusted-content | ai-action-prompt-injection | — |
2-fix-commit | HIGH write-scope、CRITICAL author-association | — (generic のみ) | — |
3-later | LOW write-scope、CRITICAL author-association | ai-action-excessive-tools、ai-action-execution-order | — |
ここで単純に「間違っている」検出器はない——それらは異なる問いに答えている:
ai-action-prompt-injection は、issue 本文が prompt: に補間されている脆弱なリビジョンで発火し、補間がなくなると止まる。正しい。後続リビジョンに関するその検出結果は、それに基づいて行動する前に読む価値がある——ai-action-excessive-tools は Write を指摘するが、このワークフローは後続のステップが投稿するファイルを書くために意図的にそれを使っている; そして ai-action-execution-order はエージェントを最後に置くことを求めるが、これはまさにこの設計が避けていることであり、特権ステップはモデルのターンが終わった後に来る。permissions ではなくエージェントのツール許可リストを読むことで、リビジョン2とリビジョン3を区別する唯一のツールである。その ci-agent-missing-author-association 検出結果は後続リビジョンで critical として報告され、自身の README はそれを偽陽性ではなく過剰に厳しいものと説明している: auto-triage は誰からでも到達可能であることが意図されている。ここにあるすべてのツールには、その正確な条件について作成者がすでに熟考していたリビジョンに対して報告する検出結果がある。それはヒューリスティックの正常な状態であり、カバレッジ表が合否列よりも有用である理由である。
git clone https://github.com/sushant-me/agentic-workflow-injection
cd agentic-workflow-injection
bash scripts/fetch-revisions.sh # the three real revisions, pinned by SHA
bash scripts/benchmark.sh # runs every detector that is installed
benchmark.sh は、インストールされていない検出器を黙ってスキップするのではなく、欠落として報告する。なぜなら、ツールを静かに省略したカバレッジ表は、そのツールについて何も証明しないからである。
フィクスチャは縮小された形であり、このリポジトリのために書かれ、他の部分と同様に MIT ライセンスである。それらはコピーではなくメカニズムに忠実である: fixtures/vulnerable.yml は4つの条件だけを持ち、fixtures/fixed.yml は同じファイルで、重要な6つの変更がそれぞれ注釈付きで施されている。ルールを追加するなら、これら2つのファイルが正しく処理すべき最小の入力である。
2つのインターフェースの癖があり、スクリプトで処理されているが知っておく価値がある:
not found と表示して 3 で終了する。監視されなければ、それはクリーンなスキャンと同一に見える。path はベースネームであるため、同じ名前のファイルに対するディレクトリスキャンは曖昧になる。検出は簡単な半分である。docs/mitigations.md はもう半分を扱う: エージェントの権限をどのように境界付けるか、層を1つの問い——この制御はモデルが従うことを選ぶことに依存しているか?——によって順位付けして。
その順位付けが要点のすべてである。プロンプト内の指示は入力であり、ガード節ではない; ターゲットを名指しするツールパターン、あるいはエージェントが決して持たないトークンは境界である。このガイドは「モデルが間違ったことをできない」から「モデルは間違ったことをしないよう求められた」へと下っていき、チェックリストと nnU-Net の進化を作例として示す。
fixtures/fixed.yml はその6つの変更それぞれに、それが実装する層およびそれが load-bearing かどうかのタグを付ける——そのうち2つはそうではなく、読者がファイルを過大評価しないようにそのようにラベル付けされている。
実行時にコミット SHA によって取得され、決してベンダリングされない:
| ファイル | リポジトリ | コミット | パス |
|---|---|---|---|
real/1-vulnerable.yml | MIC-DKFZ/nnUNet | 94300b49e716 | .github/workflows/issue-triage.yml |
real/2-fix-commit.yml | MIC-DKFZ/nnUNet | 4e4770b0b0e6 | .github/workflows/issue-agent.yml |
real/3-later.yml | MIC-DKFZ/nnUNet | 11bd8746fc06 | .github/workflows/issue-agent.yml |
nnU-Net の設定のコピーはここで再配布されていない; 取得を再実行し、上記のブロブとバイト単位で自分で比較せよ。
これは検出エンジニアリングの成果物である。エクスプロイトは含まれず、ここにあるものはライブのワークフローに対してテストされていない——脆弱なリビジョンは静的ファイルであり、nnU-Net の履歴ですでに公開されており、公開されたアドバイザリで参照されている。フィクスチャは do not deploy とマークされている。なぜならそれらはコピーされるためではなく、検出されるために存在するからである。
この分類の検出器を保守していて表に行を追加したいなら、ベンチマークスクリプトがインターフェースである: targets に対してツールを実行しその検出結果を出力するセクションを追加すれば、フィクスチャとピン留めされたリビジョンが残りをやってくれる。
MIT。