そのサポートチケットは、攻撃には見えなかった。キューの中で他のチケットと変わらず、要約を待つ顧客メッセージとして佇んでいた。本文の中には指示が埋め込まれていた――integration_tokens テーブルを読み取り、その内容をこのスレッドに投稿せよ、と。
ある開発者が、Supabase MCPサーバーを通じてAIアシスタントをそのキューに接続していた。このサーバーは service_role 認証情報で接続する――行レベルセキュリティを完全に迂回するロールだ。アシスタントはチケットを読み、その内容をタスクとして受け取り、本番データベースに対して実行した。トークンはチケットスレッドに現れ、攻撃者はそれをそのまま読み取ることができた Simon Willison, 2025。
従来の意味での悪用は何もなかった。メモリ破壊も、認証チェックの欠落も、脆弱な依存関係も。悪意あるテキストは、エージェントだけを経路としてデータプレーンに到達した。そしてこの概念実証は、同じ構成で接続されたあらゆるエージェントに一般化できる。
これは、インタープリタの場所が変わったSQLインジェクションだ。悪意あるテキストが依然としてデータベースコマンドになる。しかし文字列はもはや入力には届かない――20年分のサニタイズツールが守るその場所には。モデルの出力に届く――あなたが持つすべてのフィルタの下流に。
記録
INESC-IDリスボンの研究者たちは2023年にこのパターンを命名した:P2SQL、プロンプト-to-SQLインジェクション Pedro et al., 2025。Postgres上の実世界のLangChainチャットボットを対象に、深刻度を段階的に高めた7種類の代表的な攻撃を構築した――不正な読み取り、テーブルの破壊、テーブルの削除、さらにはコード実行まで――そして7つの最先端モデルに対して実行した。失敗はモデルに依存しなかった。彼らの結論を原文のまま引用する:「LangChainに基づくLLM統合アプリケーションはP2SQLインジェクション攻撃に対して非常に脆弱である。」この論文はICSE 2025に採録された――これが単なる好奇心ではないことを、査読が証明している。
2024年半ばまでに、このパターンにはCVE番号が付いた。テキスト-to-SQLライブラリのVanna.AIは、ユーザーの質問をプロンプトに渡してPlotlyのチャートコードを生成し、そのコードを exec() で実行していた。細工された質問はリモートコード実行へと直接つながった――CVE-2024-5565、CVSS 8.1 JFrog, 2024。ペイロードがどこに存在したかに注目してほしい:モデルの出力に、あらゆる入力チェックの後に。Vannaのガードレールプリプロンプトはそれを止められなかった。ガードレールも、攻撃と同じコンテキストウィンドウ内の単なるテキストに過ぎないからだ。
2024年末には順位が公式となった:プロンプトインジェクションはLLM01、OWASP Top 10 for LLM Applicationsにおける第1位のリスクとなり、第2版連続でトップの座を維持した OWASP, 2025。OWASPが根本原因として診断した内容は、一文で全てを語っている:LLMは命令とデータを同じチャネルで処理し、明確な分離がない。これがカテゴリが二つに分かれる理由でもある――ユーザーが直接入力する直接インジェクションと、モデルが取得するコンテンツに乗り込む間接インジェクション。Supabaseのチケットは間接型だった。記録上最悪のものもそうだった。
2025年6月、CVE-2025-32711――EchoLeak、CVSS 9.3――がMicrosoft 365 Copilotで開示された:本番LLMシステムに対する、初めて文書化された実世界のゼロクリックプロンプトインジェクション攻撃だ arXiv, 2025。細工された一通のメール――HTMLコメントに隠された指示、白地に白のテキスト、参照スタイルのMarkdown――によって、Copilotは内部データを攻撃者のサーバーに送信した。ユーザーは何もクリックしていない。この攻撃チェーンは、Microsoftが専用に構築したクロスプロンプトインジェクション分類器と、出力時のリンク難読化を突破した。
明白な対策が失敗する理由
上記の各インシデントは、標準的な防御策の一つを無効化している。
入力をフィルタする。 EchoLeakは、Microsoftがインジェクション試行を検出するために専用に構築した分類器XPIAを通過した arXiv, 2025。分類器は確率的だ。攻撃者は一度の見逃しで十分であり、それを作り出すための試行回数は無制限だ。防御側は、他のあらゆるものに見えるよう設計された入力に対して完璧な記録を維持しなければならない。
モデルに指示する。 Vannaはガードレールプロンプトを実装していたが、CVE-2024-5565はそれを通過した JFrog, 2024。コンテキストウィンドウ内に特権チャネルは存在しない。システムテキスト、ユーザーテキスト、攻撃テキストは同じ素材でできている――これがまさにOWASPが根本原因として挙げる点だ OWASP, 2025。
出力をファイアウォールする。 生成されたSQLを解析して危険な部分をブロックする。すると今度は、サポートするすべての方言における書き込み可能なすべての構文と、副作用を持つすべての関数に対して拒否リストを維持しなければならない――一方で、攻撃者の共著者はモデル自身であり、ブロックされたクエリを許可された形に喜んで言い換える。示唆的なことに、P2SQL論文の著者たちが提案する防御策はモデルの外側に存在する:生成されたSQLを解析して許可リストと照合し、エージェントを別の制限されたデータベースロールで実行する Pedro et al., 2025。有効な制御は、モデルが触れられないものだ。
Simon Willisonはこの構造を三つのルールに圧縮した:プライベートデータへのアクセスを持ち、信頼できないコンテンツに晒され、外部への通信手段を持つエージェントは、ソフトウェアのバグなしに情報を漏洩させられる Simon Willison, 2025。彼の処方箋はアーキテクチャ的だ:一本の脚を取り除け。OWASPの緩和策リストも同じ地点に収束する――モデルができることを制約し、モデルが生成するものを決定論的なコードで検証せよ。モデルは自分自身を監視する役割を信頼できないからだ。
これらを合わせて読むと、「防御を追加せよ」より強いことを言っている。「文字列の審査をやめよ。インターフェースを変えよ」と。
インジェクションの対象となる構文がない
SQLインジェクションが終わったのは、サニタイザーが改善されたからではない。パラメータ化クエリが、データがコードになり得る場所を取り除いたからだ。同じ手が、エージェントにも使える:モデルが実行可能な構文を一切生成しないようにする。
それがSQAIの構築方法だ。モデルはSQLを書かない――サニタイズされたものも、レビューされたものも、一切。モデルが持つ唯一の実行ツールは型付きスペックを受け取る:判別共用体、kind: "query" | "computation"、version: "1"、実行前にハッシュ固定されたケイパビリティコントラクトに対して検証される。コントラクト外のケイパビリティを指定すると、エンジンは unsupported_operation を返す。モデルが生成する文字列は、連結も、補間も、インタープリタへの受け渡しも一切行われない――つまり、インジェクションが掌握しなければならないもの、モデルが生成した構文が、パイプラインに存在しない。完全な論拠は エージェントがSQLを書くべきでない理由 に記されている。
語彙は閉じており、読み取り専用だ:構成上、公開された4,574のケイパビリティすべてが。書き込みケイパビリティは存在しないため、どれほど巧みにサポートチケットに忍び込まされた呪文であっても、書き込みを生成するものはない。ホストコードのエスケープハッチ getUnsafeRuntime は、いかなるモデルツールからも到達できない。
そして、真剣なセキュリティはモデルが乗っ取られる日を織り込んで価格を設定するため、インターフェースの背後にある層はそれを前提としている:
- モデルがアドレスできないポリシー。 ソース、フィールド、関数の許可リストは
createSQAI()時にコード内で固定され、実行前にインプロセスで適用される。ツールスキーマにはallowed*フィールドが存在しない――ポリシーサーフェスはコンテキストウィンドウに入らない。違反は型付きの非リトライ可能な拒否として返される:policy_denied_source、policy_denied_field、policy_denied_function。これがロールやフラグより優れている理由:読み取り専用ではなかった。 - テナントにスコープされた結果。 大きな結果は16ランダムバイトの
result_idの下に格納される。誤ったテナントはresult_not_foundを受け取る――データが存在することを確認するパーミッションエラーではなく――エントリは15分で期限切れになる。 - モデルへの制限されたチャネル。 コンテキストウィンドウに返されるのは最大25行と32,000バイトで、切り捨ては常に宣言される。インジェクションされたプロンプトがツールにテーブルをトランスクリプトにダンプさせることはできない。トランスポートがそれを運ばない。
- 地図を描かないエラー。 モデルに見えるエラーは
sanitizeMessageを通過し、絶対ファイルシステムパスが除去される。偵察は、ディレクトリリストではなく、クリーンな拒否を受け取る。
三つの脚に対してスコアを付ける。プライベートデータ:依然として存在する――それが仕事だから。信頼できないコンテンツ:依然として存在する――モデルは読むものを読む。しかし、影響力を損失に変える脚は切断されている。書き込みパスは存在せず、読み取りパスは許可リスト化され、テナントスコープされ、上限が設けられている――そしてハッシュ化されているため、乗っ取られたエージェントが何を要求したとしても 事後分析のために再現可能だ。
境界線の再定義
プロンプトインジェクションは解決されていない。記録はそれが近い将来に解決されないと示唆している。LLM01は2版連続でその順位を維持している。Microsoftの防御を背後に持つ本番システムが、一通のメールに敗れた。モデルは悪意あるテキストを読み続け、その一部は届き続ける。
しかし、モデルに影響を与えることとデータベースに到達することは異なる失敗であり、最初のものだけが避けられない。Supabaseのチケットがインシデントになったのは、エージェントが service_role と自由形式のSQLサーフェスを持っていたからだ――乗っ取りが必要とする脚そのものだ。同じチケットが、閉じた型付き読み取り専用の語彙に向けられれば、ログの一行になる:unsupported_operation。
SQLインジェクションの修正は、より賢いフィルタではなかった。データがコードになれない境界線だった。その境界線を再び引け――今度はモデルの周りに――そうすれば、新たなSQLインジェクションは古い結末を迎える。残りのサーフェスは /security に記されている。