ANSI escape sequence と聞くと、なんだか昔の端末の世界の話っぽいですが、この記事で扱っているのはかなり今っぽい問題です。Bright Security の記事は、MCP server において ANSI escape sequence injection をどう検出するかを、DAST(Dynamic Application Security Testing、動いているサービスに対して外から試すテスト)で説明しています。
これ、地味に見えてかなり面白いです。というのも、攻撃の本質が「人間には見えないけれど、AI には見える」というズレにあるからです。端末上では色を変えたり、文字を隠したり、画面を消したりする制御コードが、LLM にはただの生テキストとして読まれてしまう。ここに攻撃の余地がある、というわけです。
ANSI escape sequence は、もともと端末を制御するための命令です。たとえば文字色を変える、カーソルを動かす、画面を消す、といった操作をします。人間が普通のターミナル画面を見ると、これらのコード自体は表示されません。効果だけが見える。ここがミソです。
ところが AI agent は、見た目ではなく「生の文字列」を処理します。つまり、人間の目には空白や無害な文章に見えていても、その中に隠した命令をモデルが読んでしまう可能性がある。記事ではこのズレを、攻撃の入口として説明しています。
個人的には、ここがこの話のいちばん嫌らしいところだと思います。人間のレビューをすり抜けるのに、モデルだけがきっちり読んでしまう。昔からある「表示のトリック」が、AI 時代にそのまま再利用されている感じです。
記事は MCP(Model Context Protocol)を舞台にしています。MCP は、AI agent が外部の tool や resource、prompt にアクセスするための仕組みです。要するに、モデルに「この文書を読んで」「この URL を要約して」といったことをさせるための通路です。
問題は、この通路に流れる text の中に、ANSI 制御コードを混ぜ込めることです。攻撃者は、モデルが読むデータの中に隠し命令を仕込み、モデルだけがその指示に従うように持っていけるかもしれない。人間の監督者には見えないのに、AI はそのまま受け取る。かなりいやな構図です。
記事ではこの種の問題を、出力の無害化に失敗した例としても位置づけています。たとえば kubectl や Git の過去の脆弱性のように、端末制御コードをそのまま出してしまうと、表示の偽装につながります。MCP ではそれがさらに AI の行動に影響する、という点が新しいです。
記事が面白いのは、攻撃を2種類に分けているところです。ひとつは direct-fetch AESI、もうひとつは stored AESI です。
direct-fetch AESI は、MCP server が URL を取りに行って、その内容をモデルに渡すタイプの攻撃です。たとえば「このページを読んで要約して」という tool があるとします。攻撃者は、自分の管理する URL に ANSI コード入りの内容を置き、それをその tool に読ませる。すると server がその内容をモデルに返し、モデルは隠し命令まで読んでしまうかもしれない。
stored AESI は、もっと厄介です。これは一度どこかに書き込んだ payload が、あとから別の場所で読み返されたときに発火するタイプです。たとえば note や record に保存しておき、後で別の tool がその内容を引いてくる、という流れです。こうなると、攻撃は持続します。1回の書き込みが、あとから何度も悪さをする可能性がある。
ここはかなり重要です。stored 型は「書いた瞬間には何も起きない」のが怖い。後から別の処理が勝手に火をつける。しかも write と read が別の entrypoint だったり、プロトコルが違ったりするので、見つけるのが難しい。人間の感覚だと完全に別問題に見えるのに、実際はつながっている。このあたり、現場では本当に見落としやすいと思います。
記事で挙げられている影響は、単なる「変な文字が混じる」では済みません。
AI agent が attacker-controlled な指示に従って tool を呼び出したり、情報を漏らしたり、分析結果を汚したりする可能性があります。しかも、human-in-the-loop、つまり人間の確認を挟む運用でも、ANSI の concealment によって表示上は無害に見せかけられる。監督者は安心して承認してしまうかもしれないのに、モデルの中では別の命令が生きているわけです。
さらに、画面消去やカーソル移動の制御コードは、ログや監査記録の見た目まで崩せます。これはかなり実務的に嫌です。監査ログが見づらい、あるいは誤解を招く。インシデント対応で確認すべき情報が、見かけ上きれいに整形されてしまう可能性があるからです。
stored AESI だと、shared knowledge base のような場所に1件汚れたデータが入るだけで、後続の agent workflow 全体に影響することがあります。ここは「単発の脆弱性」ではなく「データ汚染の持続化」だと思うと、かなり印象が変わります。
記事の本題はここです。Bright Security は、こうした攻撃を DAST で自動検出する考え方を説明しています。
DAST はソースコードを見ずに、実際に動いているシステムへ入力を与え、応答を観察する方式です。黒箱テストとも言えます。今回のように、最終的に payload がどう流れるかは実行時の挙動に依存するので、DAST と相性がいい。むしろ静的解析だけでは取りこぼしやすい種類の問題です。
検出の基本はシンプルで、3つのサインが同じ model-consumable field に残っているかを見ることです。ここでいう model-consumable field は、モデルが実際に読む場所のこと。たとえば tool の result.content[].text や resource の contents[].text、prompt の messages[].content.text などです。JSON のどこかにあればいいのではなく、モデルに入る場所でなければ意味がない、という考え方です。
その3つとは、ANSI escape byte、固定の instruction phrase、そして payload ごとの trigger marker です。これが同じ field に残っていたら、その field は脆弱だと判断します。逆に言うと、どれか1つでも欠けたら除外する。この条件を厳しくしているのが、誤検知を減らす肝です。
私はこの設計、かなり筋がいいと思いました。セキュリティ検査って、雑にやると「怪しいもの全部引っかける」方向に流れがちですが、記事の方法は「本当にその payload が、その field で生き残ったのか」を丁寧に確かめている。地味だけど大事です。
direct-fetch 側では、MCP の action-request entrypoint を対象にします。tool、resource、prompt の読み取り系です。しかも「URL っぽいパラメータだけ」を狙うのではなく、string や URL、email、phone、file URI のような広めの text parameter をまとめて候補にする。意外とこういう広く取る姿勢が実戦的です。名前で判断すると取りこぼすので、まず試す、という発想ですね。
検査の流れは、payload の hosted URL を候補パラメータに入れて呼び出し、返ってきた response を raw bytes のまま保存し、モデルが読む field を辿って3条件を確認する、というものです。返答がエラーなら、その時点では finding にしません。あくまで「実際に payload が通ったものだけ」を報告する。
ここで重要なのは、response の見た目を整形してから見ないことです。ANSI の本体は byte 列なので、途中で rendering や sanitizing を挟むと痕跡が消えます。これはセキュリティ検査の基本ですが、今回ほど「生のまま扱う」ことが大事な例も珍しいと思います。
stored AESI では、どこに書いたデータが、どこで読み返されるのかを先に探る必要があります。記事はここを2段階でやっています。
まず probe。各 injection entrypoint に対して、無害な marker を1つずつ埋め込んで書き込みます。これで後から誰の書き込みがどこに出たかを追跡できるようにします。次に、その marker がどの read surface で再登場するかを総当たりで確認する。ここで「この write は、この read に返ってくる」という地図を作るわけです。
この地図ができると、ようやく本物の payload を投げられます。しかも、ただ投げるのではなく、probe で確認済みの field path に対してだけ検査する。これがいい。むやみに広く探すより、すでに「ここが model-consumable だ」と分かった場所に絞るので、精度が上がる。
さらに面白いのは、write 側は HTTP でも MCP でもよく、read 側は MCP の model-consumable field でなければならない、という整理です。つまり、書き込み経路と読み出し経路がプロトコルをまたぐことすらある。ここは本当に実務で見落としやすいところでしょう。システムの境界をまたいでデータが再利用されると、人間の頭の中の「別機能」が、攻撃の上ではひとつの流れになります。
この記事が示しているのは、MCP 時代のセキュリティは「プロンプトに変な文が入るか」だけでは足りない、ということです。端末制御コードのような古典的な文字列トリックが、AI agent の文脈では新しい攻撃面になる。しかも、黒箱テストでなければ追いにくい。
個人的には、これは「AI 特有の新種の脆弱性」というより、「古い表示トリックが、AI の読み取り層で再発明された」話だと感じます。だからこそ厄介です。新語で片づけると見誤るし、逆に昔話だと思って軽視しても危ない。
MCP server を運用している側からすると、単に入力検証を頑張るだけでは足りません。どの field がモデルに届くのか、その経路のどこで raw text がそのまま流れてしまうのかを、実際に試して確かめる必要がある。Bright Security の記事は、その現実的なやり方をかなり具体的に示していて、読んでいて納得感がありました。
参考: Detecting ANSI Escape Sequence Injection in MCP Servers with DAST | Bright Security