MCPのtool poisoning対策:ラグプル・シャドーイングを検知し被害を封じ込める方法【MCPセキュリティ・2026】
MCPのtool poisoningを、説明文のポイズニング・ラグプル・シャドーイング・出力経由の注入の4パターンで整理。MCPToxの数値、2026-07-28版仕様の該当ルール、実際のCVE、ツール定義をハッシュで固定する検知スクリプト、被害を封じ込める18項目のチェックリストを解説します。
最終更新:2026年10月15日 / 対象:MCP仕様 2026-07-28版(Tools、Security Best Practices)、OWASP MCP Top 10(2025年版・ベータ)
結論(30秒で読める要約)
MCPのtool poisoning(ツールポイズニング)とは、ツールの説明文やパラメーター説明、スキーマ、またはツールの実行結果に「モデル向けの指示」を紛れ込ませ、ユーザーが頼んでいない操作をAIエージェントにさせる攻撃です。OWASP MCP Top 10では3番目(MCP03)に挙げられています。ベンチマークMCPToxでは、20モデルに対する攻撃成功率が最大72.8%、モデルが拒否した割合は最も高いモデルでも3%未満でした。
モデル側で完全に防ぐ方法はありません。MCPセキュリティとしては、次の3層で守るのが現実的です。
- ツール定義の変化と危険な記述を検知する。承認したツール定義をハッシュで固定し、セッション開始のたびに比較する。新しい定義は、モデル向けの命令文・不可視文字・機密ファイルのパスがないか静的にチェックする。
- エージェントのセッションごとに「致命的な三要素(lethal trifecta)」をそろえない。機密データへのアクセス、信頼できない入力、外部への送信手段を1つのセッションに同時に持たせない。
- 乗っ取られても被害が小さくなるように設計する。ツール単位のスコープをサーバー側で強制し、外部送信や破壊的な操作には人の承認を挟み、Egress(外向き通信)を許可リストで絞り、どのツールが呼ばれたかをログに残す。
この記事では、4つの攻撃パターンとそれぞれがプロトコルのどこに入り込むか、今日から使える検知スクリプト、社内ツールをMCPで公開するチーム向けの封じ込めチェックリストをまとめます。
目次
- MCPのtool poisoningとは
- 4つのパターン:ポイズニング・ラグプル・シャドーイング・出力経由の注入
- モデルはどのくらい騙されるのか(数値)
- MCP仕様(2026-07-28版)が定めていること・定めていないこと
- 実際の事例とCVE
- 検知:ツール定義を固定・比較・スキャンする
- 封じ込め:毒入りツールに大したことをさせない設計
- MCPサーバーを公開する側の注意点
- 対策チェックリスト(18項目)
- よくある失敗(症状→原因→対処)
- NodeFlareならこう解決できる
- よくある質問(FAQ)
1. MCPのtool poisoningとは
MCPのtool poisoningとは、モデルが「信頼できるツール情報」として読むデータ、主に tools/list が返す description に指示を埋め込む、間接的なプロンプトインジェクションの一種です。Invariant Labsが2025年4月1日に命名し、こうした指示はユーザーからは見えないがモデルには読まれる、と指摘しました。
原因はMCPの構造にあります。MCPのツールは「モデルが制御する(model-controlled)」ものとして設計されており、クライアントは各ツールの名前・説明・入力スキーマをモデルに渡して、どれを呼ぶかを判断させます。つまり、サーバーがこれらの欄に書いた内容はそのままモデルのコンテキストに入ります。悪意のあるサーバーや乗っ取られたサーバーは、自分のツールが一度も呼ばれなくてもエージェントの判断に影響を与えられます。
OWASP MCP Top 10では、これを MCP03:2025 Tool Poisoning として挙げています。また、取得したコンテンツやツールの実行結果がユーザーの目的を途中で書き換えてしまう攻撃は MCP06:2025(個別ページでは「Intent Flow Subversion」)として別に扱っています。
認証、トークンパススルー、SSRF、サプライチェーンなどを含むMCPセキュリティの全体像は、MCPセキュリティ対策チェックリスト25項目にまとめています。この記事はその中の1項目を深掘りしたものです。
2. 4つのパターン:ポイズニング・ラグプル・シャドーイング・出力経由の注入
4つのパターンは、悪意ある文章がどこに置かれ、いつ現れるかが違います。それぞれ破られる対策が異なるため、区別しておくことが大切です。
| パターン | 仕込まれる場所 | 現れるタイミング | 無効化される対策 | 一次情報 |
|---|---|---|---|---|
| 説明文のポイズニング | ツールの description、パラメーター説明などスキーマの各欄 |
インストール時・最初の tools/list |
説明文の原文を読まないユーザー | Invariant Labs(2025年4月)、OWASP MCP03 |
| ラグプル(rug pull) | 同じ欄を後から書き換える | ユーザーやチームが承認した後 | 一度きりのレビューと承認 | Invariant Labs(2025年4月)、CSA(2026年5月) |
| サーバー間のシャドーイング | あるサーバーの説明文が別サーバーのツールに言及する | 両方のサーバーが同じセッションにあるとき | 正常なサーバーへの信頼(攻撃側のツールは呼ばれない) | Invariant Labs(2025年4月) |
| 出力経由の注入 | ツールの実行結果(Issue本文、メール、Webページ、DBの行など) | ツール呼び出し時 | ツール定義のレビューすべて | OWASP MCP06、Invariant LabsのGitHub MCP調査(2025年5月) |
混同されやすい関連の問題も2つあります。
- ツール名の衝突(squatting):2つのサーバーが同じ名前や似た名前のツールを公開する問題です。仕様は、複数サーバーのツールを集約するクライアントは「サーバー識別子を接頭辞にするなどの区別方法を実装すべき(SHOULD)」とし、
serverInfo.nameは一意である保証がないとしています。 - 設定ファイルのラグプル:ツールの説明文ではなく、ローカルサーバーを起動するコマンドそのものが承認後にすり替えられる問題です。代表例は5章のCVE-2025-54136です。
3. モデルはどのくらい騙されるのか(数値)
ツールの説明文は開発者からの正式な指示に見えるため、現在のモデルでもtool poisoningは高い確率で成功します。公開されている中で最も規模の大きい測定はMCPTox(arXiv 2508.14925、2025年8月)です。
| 項目 | 結果 |
|---|---|
| テストした実在のMCPサーバー | 45 |
| ツール数 | 353 |
| テストケース | 1,312 |
| 評価したモデル | 20 |
| 最も高い攻撃成功率 | 72.8%(o1-mini) |
| 最も高い拒否率 | 3%未満(Claude 3.7 Sonnet) |
| 性能とリスクの関係 | 性能の高いモデルほど騙されやすい傾向がある |
ここから言えることは2つです。
- モデルが拒否してくれることを前提にしてはいけません。最も慎重なモデルでも、100回中3回も拒否しませんでした。
- 確率的なフィルターは「層の1つ」であり、境界にはなりません。Simon Willison氏は「攻撃の95%を防ぐ」とうたうガードレール製品について、Webアプリのセキュリティなら95%は不合格だと指摘しています。MicrosoftのMSRCブログ(2025年7月29日)も同じ考え方で、Spotlighting(信頼できない入力の区別)やPrompt Shieldsといった確率的な対策に加えて、データガバナンス、既知の持ち出し手法の決定的なブロック、人による承認といった確実に効く対策を組み合わせています。
4. MCP仕様(2026-07-28版)が定めていること・定めていないこと
MCP仕様には「tool poisoning」という章はありません。Security Best Practicesのページが扱うのは、confused deputy、トークンパススルー、SSRF、state handleの乗っ取り、ローカルサーバーの侵害、スコープの最小化です。tool poisoningに関係するルールはToolsのページに散らばっているので、ここに集めます。
| ルール | 仕様の内容(2026-07-28版) | 対象 |
|---|---|---|
| 人の関与 | ツール呼び出しを拒否できる人を常に介在させるべき(SHOULD) | アプリケーション |
| 見える化 | どのツールがAIに公開されているかを明示し、呼び出し時に表示し、確認プロンプトを出すべき(SHOULD) | アプリケーション |
| アノテーションは信頼しない | 信頼できるサーバー以外から来たツールのアノテーションは信頼できないものとして扱わなければならない(MUST) | クライアント |
| 入力の表示 | 悪意ある、または意図しないデータ持ち出しを防ぐため、サーバーを呼ぶ前にツールの入力をユーザーに見せるべき(SHOULD) | クライアント |
| 結果の検証 | ツールの結果をLLMに渡す前に検証し、監査のためにツールの使用をログに残すべき(SHOULD) | クライアント |
| サーバーの基本 | すべての入力を検証し、アクセス制御を実装し、呼び出し回数を制限し、出力をサニタイズしなければならない(MUST) | サーバー |
| 名前の衝突 | 集約するクライアントはサーバー識別子の接頭辞などで区別すべき(SHOULD) | クライアント・プロキシ |
| 変更の通知 | listChanged を宣言したサーバーは、ツール一覧の変更時に notifications/tools/list_changed を送るべき(SHOULD) |
サーバー |
| 呼び出し元ごとの一覧 | ツール一覧はリクエストの認可内容によって変えてよい(MAY)。例:呼び出し元のスコープで使えるツールだけを返す | サーバー |
実務上のポイントは次のとおりです。
readOnlyHintやdestructiveHintはあくまでヒントです。信頼できないサーバーのアノテーションは信頼しないことが仕様上のMUSTなので、「読み取り専用」と書いてあるだけで自動承認してはいけません。list_changedはラグプルの目安にはなりますが、保証にはなりません。行儀のよいサーバーは変更を知らせてくれますが、悪意あるサーバーは知らせないかもしれません。セッション開始ごとに一覧を取り直して比較する方法(6章)なら、サーバーの誠実さに頼らずに済みます。- 仕様にはツール定義の署名や改ざん検知の仕組みがありません。OWASP MCP03は署名付きスキーマとコンテンツハッシュを推奨していますが、クライアントが標準で対応するまでは、自分でハッシュを固定する必要があります。
5. 実際の事例とCVE
それぞれのパターンが実際の製品で起きた例です。内容はアドバイザリや公表資料に書かれている範囲にとどめます。
| 時期 | 事例 | パターン | 何が起きたか | 教訓 |
|---|---|---|---|---|
| 2025年4月 | Invariant Labsの公表 | 説明文のポイズニング、シャドーイング | 毒入りサーバーと正規のWhatsApp MCPサーバーを組み合わせると、メッセージ履歴を持ち出せた(CSAの整理による) | 素性の分からないサーバーを、機密データを扱うサーバーと同じセッションに入れない |
| 2025年5月 | Invariant Labs、GitHub MCP | 出力経由の注入 | 悪意ある公開Issueによって、権限の広いエージェントが非公開リポジトリのデータを漏らし得た | トークンをリポジトリ単位に絞り、信頼できない入力と非公開データを分ける |
| 2025年8月1日 | CVE-2025-54136(MCPoison)、Cursor 1.2.4以下 | 設定ファイルのラグプル | 一度信頼したMCP設定を書き換えると、警告や再確認なしに別のコマンドが実行された(NVD CVSS 3.1:8.8) | 設定が変わるたびに再承認する。Cursor 1.3で修正 |
| 2025年8月4日 | CVE-2025-54135(CurXecute)、Cursor 1.3.9未満 | 出力経由の注入→設定ファイルの作成 | 間接プロンプトインジェクションで、承認なしに .cursor/mcp.json を新規作成でき、コード実行につながった(NVD CVSS 3.1:9.8) |
MCP設定ファイルを保護対象として扱う。1.3.9で修正 |
| 2025年9月 | npmの postmark-mcp 1.0.16 |
パッケージ単位のラグプル | 1行の変更で、送信する全メールのBCCに攻撃者のアドレスが追加された | バージョンを固定し、初回だけでなく更新時も差分をレビューする |
5件に共通するのは、最初のレビューでは問題がなく、被害は「後から変わったもの」か「実行時に届いたもの」から生じた点です。一度きりの承認が一番の弱点です。
6. 検知:ツール定義を固定・比較・スキャンする
検知の基本は、サーバーがいま公開している内容を承認時の内容と比べ、ツール定義に本来あるはずのない記述を見つけることです。Cloud Security Alliance(CSA)の2026年5月のホワイトペーパーは、登録時の静的解析を「最も信頼できる予防策」と位置づけたうえで、承認後の変更は静的チェックでは見逃すため、セッション間でのスキーマ変更検知を組み合わせるよう勧めています。
6-1. 静的にチェックすべき危険な記述
OWASP MCP03の検知セクションは、ツール名・説明文・パラメーター説明について5つの兆候を挙げています。
| 兆候 | 確認すること |
|---|---|
| モデルに向けた命令文 | ツールの説明ではなく、モデルに話しかける文章(それまでの指示を無視させるような文など) |
| 機密パスへの言及 | ~/.ssh や .env など、認証情報の置き場所 |
| 持ち出しのパターン | send・post・uploadといった動詞と外部の送信先の組み合わせ |
| 不可視の文字 | ゼロ幅文字や双方向制御文字 |
| コメントに隠した指示 | HTMLやMarkdownのコメント内にあるモデル向けの文章 |
MCPならではのチェックとして、さらに2つ加えるのがおすすめです。ツールの役割に比べて説明文が不自然に長くないか、そして説明文が他のサーバーのツール名に触れていないか(シャドーイングの兆候)です。
6-2. ツール定義をハッシュで固定する
Invariant Labsは、クライアントがMCPサーバーとそのツールのバージョンを固定し、説明文をハッシュやチェックサムで検証することを推奨しています。次のスクリプトは、保存した tools/list の応答に対してこれを行います。モデルに渡る欄のハッシュを取ってベースラインと比べ、OWASPの兆候もチェックします(Python 3.13で動作確認済み)。
import hashlib, json, re, sys, unicodedata
# Fields that reach the model or change how a tool behaves
PINNED_FIELDS = ("name", "title", "description", "inputSchema", "outputSchema", "annotations")
# Static indicators adapted from OWASP MCP03 (detection section)
INDICATORS = {
"hidden_chars": re.compile("[---]"),
"comment_block": re.compile(r"<!--|-->"),
"model_directed": re.compile(r"\b(ignore (all |any )?(previous|prior) instructions|do not (tell|mention|inform) the user|before using (any|this) tool)\b", re.I),
"sensitive_path": re.compile(r"(~/\.ssh|id_rsa|\.env\b|\.aws/credentials|mcp\.json)", re.I),
}
def fingerprint(tool: dict) -> str:
pinned = {k: tool.get(k) for k in PINNED_FIELDS}
canonical = json.dumps(pinned, sort_keys=True, ensure_ascii=False, separators=(",", ":"))
return hashlib.sha256(unicodedata.normalize("NFC", canonical).encode()).hexdigest()
def scan_text(tool: dict) -> list[str]:
text = json.dumps(tool, ensure_ascii=False)
return [name for name, rx in INDICATORS.items() if rx.search(text)]
def main(tools_path: str, baseline_path: str) -> int:
data = json.load(open(tools_path, encoding="utf-8"))
tools = data.get("result", data).get("tools", [])
current = {t["name"]: fingerprint(t) for t in tools}
try:
baseline = json.load(open(baseline_path, encoding="utf-8"))
except FileNotFoundError:
json.dump(current, open(baseline_path, "w"), indent=2)
print(f"Baseline created with {len(current)} tools. Review them before approving.")
baseline = current
problems = 0
for name in sorted(set(current) | set(baseline)):
if name not in baseline:
print(f"NEW {name}"); problems += 1
elif name not in current:
print(f"REMOVED {name}"); problems += 1
elif current[name] != baseline[name]:
print(f"CHANGED {name}"); problems += 1
for t in tools:
hits = scan_text(t)
if hits:
print(f"REVIEW {t['name']}: {', '.join(hits)}"); problems += 1
return 1 if problems else 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1], sys.argv[2]))
使い方:サーバーの tools/list の応答を tools.json として保存し(ゲートウェイのログ、MCP Inspector、クライアントのデバッグ出力などから取得)、python mcp_tool_baseline.py tools.json baseline.json を実行します。初回はベースラインを書き出すので、その内容を人が確認してから承認します。2回目以降は、ツールの追加・削除・変更が1つでもあれば終了コード1で終わるため、CIのジョブやセッション開始を止める条件に使えます。tools/list がページ分割されている場合は、全ページを結合してから渡してください。
限界も押さえておきましょう。正規表現で見つかるのは雑に書かれた攻撃だけで、巧妙なものはすり抜けます。ハッシュも「何かが変わった」ことしか教えてくれず、その変更が安全かどうかは分かりません。CHANGED と出たツールは、必ず人が差分を確認してください。
6-3. 既製のスキャナー
Snykの「Agent Scan」(旧名 mcp-scan、Apache-2.0ライセンス)は、インストール済みのMCPサーバーやエージェントのスキルを対象に、プロンプトインジェクション、tool poisoning、ツールシャドーイング、「toxic flows」などのリスクをスキャンします。ただし、ツール名・説明文・スキルの内容を同社の解析APIに送信します(設定値などのシークレットは送信前に伏せ字にされます)。利用には SNYK_TOKEN が必要です。社内サーバーに使う前に、自社のデータ取り扱いルールに合うか確認してください。
6-4. ログに現れる実行時のサイン
ツール定義の確認だけでは半分です。出力経由の注入は、呼び出しの時点で初めて表に出ます。次のサインはアラートにする価値があります。
| サイン | 考えられる原因 |
|---|---|
| ユーザーの依頼と関係のないツールが呼ばれた | 出力経由の注入やシャドーイングによる誘導 |
普段使わないツールでの認可エラー(403、スコープ拒否) |
乗っ取られたエージェントが権限の外を探っている |
| 読み取りだけの作業の後に、書き込みや外部送信が続いた | 目的のずれ(OWASP MCP06のintent drift) |
| サーバーから新しい外部の宛先への通信 | データ持ち出しの試み、またはラグプルされた依存パッケージ |
自分たちがリリースしていないのに tools/list の結果が変わった |
ラグプル |
7. 封じ込め:毒入りツールに大したことをさせない設計
封じ込めは「いずれ毒入りの指示に従ってしまう」ことを前提に、従っても大したことができない状態を作る考え方です。確実に効く対策はここにあります。
7-1. セッションごとに「致命的な三要素」をそろえない
Simon Willison氏が2025年6月16日に提唱した「lethal trifecta(致命的な三要素)」は、設計ルールとして最も分かりやすいものです。機密データへのアクセス、信頼できないコンテンツへの接触、外部と通信する手段の3つを持つエージェントは、騙されてデータを漏らし得ます。同氏は、MCPが異なる出どころのツールを自由に組み合わせることを促してしまう点も問題として挙げています。セッション単位の判定表にすると次のようになります。
| セッションの種類 | 機密データ | 信頼できない入力 | 外部への送信 | 判定 |
|---|---|---|---|---|
| 調査用エージェント(Web検索、公開ドキュメント) | なし | あり | あり | OK |
| 社内分析(CRMの読み取り、社内Wiki) | あり | なし | なし | OK |
| 顧客メールを読み、メール送信もできるサポート用エージェント | あり | あり | あり | 分割するか、送信に人の承認を挟む |
| 公開Issue+非公開リポジトリ+push権限を持つコーディングエージェント | あり | あり | あり | トークンを1リポジトリに絞り、pushを承認制にする |
7-2. ツール単位の最小権限をサーバー側で強制する
毒入りの指示が使えるのは、そのセッションが呼べるツールだけです。エージェントやジョブごとに資格情報を1つ発行し、使うツール名でスコープを絞り、ツールが実行される前にサーバー側でチェックします。読み取り用と書き込み用のツールを分ければ、読み取り専用のエージェントに書き込み権限を渡さずに済みます。スコープ付きキーの発行やローテーションの手順は、MCPのAPIキー管理の記事で解説しています。
呼べないツールは見せないことも有効です。仕様は tools/list を呼び出し元の認可によって変えることを認めているので、エージェントが呼べないツールをコンテキストに入れる必要はありません。悪意のある、あるいは雑なサーバーがモデルの前に置ける説明文の量が減り、トークンの節約にもなります(参考:MCPのトークン消費の内訳と削減方法)。
7-3. 外部送信と破壊的な操作は人が承認する
データを外に送るツールや、取り消せない操作をするツールには、仕様のSHOULDに沿って人の承認を挟み、仕様が勧めるとおり引数をすべて表示します。readOnlyHint を根拠に自動承認するのはやめましょう。信頼できないサーバーのアノテーションは信頼しないのが仕様の前提です。
7-4. サーバーの通信先を絞る
Egressの許可リストがあれば、「任意のホストにデータを送る」試みは、ブロックされてログに残る接続になります。Microsoftの言う「被害を決定的にブロックする」対策の代表がこれです。まずはログだけ取るモードで各サーバーが本当に必要な宛先を集め、その後でブロックを有効にします。
7-5. サーバーを分離し、設定変更をコード変更として扱う
サーバーごとに別のサンドボックスで動かし、それぞれ自分のシークレットだけを持たせます。こうすれば、ラグプルされたサーバーが他のサーバーの認証情報を読むことはできません。ローカルサーバーについて仕様は、「最小限の権限のサンドボックス環境で」実行すべき(SHOULD)としています。CVE-2025-54135とCVE-2025-54136を踏まえ、MCP設定ファイル(.cursor/mcp.json やプロジェクトの .mcp.json など)の変更は、CSAの2026年7月のレポートが勧めるとおりコードレビューの対象にしましょう。
7-6. バージョンを固定してレビューする
サードパーティのサーバーは latest ではなく正確なバージョンを指定してインストールし、更新前にツール説明文の変化も含めて差分を確認します。postmark-mcp の変更は、パッチリリースのたった1行でした。
8. MCPサーバーを公開する側の注意点
社内SaaSなどをMCPで公開すると、自分たちのサーバーの説明文や出力が、他の人のエージェントのセッションに入り込みます。自分のサーバーが「毒の運び手」にならないためのルールです。
| ルール | 理由 |
|---|---|
| 説明文は事実だけを短く書き、モデルに向けた方針めいた指示を書かない | スキャナーやレビューが機能し、誤検知も避けられる |
| 他のサーバーのツールに説明文で言及しない | 善意でもシャドーイングと同じ形になる |
listChanged を宣言し、ツールを変えるたびに list_changed を送る |
クライアントが変更を正しく検知できる。説明文の変更は変更履歴にも書く |
| ツールを常に同じ順番で返す | 仕様のSHOULDであり、差分の比較も安定する |
出力をサニタイズし(仕様のMUST)、可能なら outputSchema と structuredContent を使う |
型の決まった項目は、自由記述のテキストより注入の余地が小さい |
| 結果に含まれるユーザー投稿(Issue本文など)は「外部の作成者によるデータ」と分かるようにする | クライアントがSpotlightingのような区別をしやすくなる |
tools/list を呼び出し元のスコープで絞る |
各エージェントには呼べるツールだけが見える |
9. 対策チェックリスト(18項目)
棚卸しとサプライチェーン
- [ ] 使っているMCPサーバーをすべて把握している(プロジェクト単位の設定ファイルも含め、野良サーバーがない)
- [ ] サードパーティのサーバーは正確なバージョンで固定し、更新は差分でレビューしている
- [ ] MCP設定ファイルの変更にはレビューと再承認が必要になっている
- [ ] IDEやクライアントは、CVE-2025-54135とCVE-2025-54136の修正版(Cursorなら1.3.9以上)になっている
検知
- [ ] 承認したツール定義をハッシュで固定し、不一致ならセッションやCIを止める
- [ ] 新しい定義を、モデル向けの命令文・不可視文字・コメント・機密パスについてスキャンしている
- [ ]
CHANGEDになったツールは、人が差分を確認してから再承認している - [ ] 想定外のツール呼び出し、スコープ拒否、新しい外部の宛先でアラートが出る
封じ込め
- [ ] 機密データ・信頼できない入力・外部送信の3つを、承認なしで1つのセッションに同時に持たせていない
- [ ] エージェントやジョブごとに資格情報を1つ発行し、ツール名単位のスコープをサーバー側で強制している
- [ ]
tools/listは、呼び出し元が呼べるツールだけを返している - [ ] 外部送信と破壊的な操作は、引数を全部見せたうえで人が承認している
- [ ] 信頼できないサーバーのアノテーションを自動承認の根拠にしていない
- [ ] 各サーバーにEgressの許可リストがある
- [ ] 各サーバーは分離され、自分のシークレットだけを持っている
サーバーを公開している場合
- [ ] 説明文は事実のみで短く、他のサーバーのツールに触れていない
- [ ] ツールを変えるたびに
list_changedを送り、説明文の変更も変更履歴に書いている - [ ] 出力をサニタイズし、可能な範囲で
outputSchemaで型を付けている
10. よくある失敗(症状→原因→対処)
| 症状 | 原因 | 対処 |
|---|---|---|
| レビュー済みのサーバーが数週間後に違う動きをする | 一度きりの承認で、定義を固定していない | 定義をハッシュで固定し、セッション開始ごとに比較する |
| 信頼しているサーバーのツールが、妙な引数で呼ばれる | 同じセッションにいる別サーバーからのシャドーイング | セッションを分け、ツール名をサーバーごとに区別する |
| 読み取りだけの調査タスクなのに、最後にメールが送られていた | 出力経由の注入と、送信権限を持ったセッション | そのセッションから送信ツールを外すか、承認制にする |
| 自動承認で「読み取り専用」のはずのツールがデータを書き換えた | 信頼できないサーバーの readOnlyHint を信じた |
アノテーションではなく自社のポリシーで承認を決める |
| どのツールからデータが漏れたのか誰にも分からない | 呼び出しごとのログがない | 呼び出し元、ツール、結果、ステータスを毎回記録する |
| スキャナーは何も検出しなかったのに乗っ取られた | 攻撃が定義ではなくツールの出力から来た | 封じ込めの対策を足す。定義のスキャンは出力をカバーしない |
11. NodeFlareならこう解決できる
NodeFlareは、MCPサーバーをプロキシの後ろで動かし、すべてのリクエストをコードに届く前にチェックします。毒入りの文章にモデルが説得されること自体は防げませんが、6-4章と7章で挙げた「確実に効く封じ込め」と「ログ」の層を用意できます。この記事で挙げたリスクと、ドキュメントに記載されている機能の対応は次のとおりです。
| この記事のリスク | NodeFlareの機能 | 内容 |
|---|---|---|
| 乗っ取られたエージェントが担当外のツールを呼ぶ(7-2) | ツール単位のスコープ | MCPのメソッドと対象ごとにスコープをチェックします。tools:list、tools:call(全ツール)、tools:call:<ツール名>(1つのツール。例:tools:call:get_weather)、resources:read:<uri>、prompts:get:<name>。拒否されたリクエストにはJSON-RPCエラーが返り、サーバーには届きません |
| モデルのコンテキストに入る説明文が多すぎる(7-2) | tool_list_filter_by_scope |
tools/list には呼び出し元が呼べるツールだけを表示します。キャッシュのキーに呼び出し元の識別情報が含まれるため、絞り込んだ一覧が他の資格情報に漏れることはありません |
| 注入が成功した後のデータ持ち出し(7-4) | Egress制御 | モードは off(初期値)、observe(宛先を記録)、enforce(リストにない宛先をブロック)。登録は最大100件(完全なホスト名、先頭1つのワイルドカード *.example.com、IP)。ホスト側のDNSとnftablesで制御します |
| ラグプルされたサーバーが他の認証情報を読む(7-5) | Firecracker microVMによる分離と暗号化されたシークレット | サーバーごとに専用のFirecracker microVMで動きます。シークレットは保存時に暗号化され、VM起動時にだけ環境変数として注入されます。ビルド引数、イメージのレイヤー、ログには書き込まれません |
| モデルが書いたコードが注入された指示に従う | コードモードのサンドボックス | コードはDenoのサブプロセスで実行され、ファイルシステム・環境変数・プロセス生成は使えません。通信先はコールバック先のホストだけで、サンドボックスからのツール呼び出しはサーバー側で改めてスコープチェックされます |
| 実行時のサインが取れない(6-4) | リクエストログ | 呼び出しごとに tool_name、arg_keys(キー名のみで値は保存しない)、outcome(ok、scope_denied、rate_limited など)、caller_kind、client_ip、http_status を記録します。ツール名や結果で検索でき、保存期間はFreeが7日、Pro/Teamが30日です |
| 盗まれたキーが別の場所から使われる | ネットワークアクセス制限 | ポリシーを restricted にして、許可するIPv4/IPv6のCIDRを登録します。認証より前にチェックされ、リストが空ならすべて拒否します |
| エージェントの暴走ループ | レート制限 | IPごとの毎分リクエスト数の上限と、ワークスペースごとの月間クォータがあります |
自分たちで行う必要があること:NodeFlareは、サードパーティのツール説明文のスキャンやハッシュによる固定は行わず、モデル内部でのプロンプトインジェクションも防げません。クライアント側での人による承認の代わりにもなりません。6章の検知と7-3章の承認ルールと組み合わせて使ってください。
最小の始め方:
- MCPサーバーをデプロイし、エージェントごとに必要な
tools:call:<ツール名>だけを持つAPIキーを発行する。 tool_list_filter_by_scopeを有効にし、Egress制御をobserveにする。- 1週間後、Logs > Egress で想定どおりの宛先を許可し、
enforceに切り替える。
1サーバーまで無料で試せます:NodeFlareを始める
12. よくある質問(FAQ)
MCPのtool poisoningとは何ですか?
ツールの説明文やスキーマにモデル向けの指示を仕込み、ユーザーが頼んでいない操作をAIエージェントにさせる攻撃です。モデルは説明文を読みますが、ユーザーが原文を目にすることはほとんどありません。OWASP MCP Top 10ではMCP03として挙げられています。
MCPのラグプル(rug pull)とは?
承認した後でサーバーがツール定義を書き換え、レビュー済みのツールを悪意あるものに変える攻撃です。一度きりのレビューでは防げません。承認した定義をハッシュで固定し、セッション開始ごとに比較すれば検知できます。
MCPのツールシャドーイングとは?
あるサーバーのツール説明文が、別の信頼しているサーバーのツールの使われ方を変えてしまう攻撃です。攻撃側のツールは一度も呼ばれないこともあります。信頼できないサーバーを、機密を扱うサーバーと同じセッションに入れないことが対策になります。
tool poisoningとプロンプトインジェクションの違いは?
tool poisoningは間接プロンプトインジェクションの一種で、ツールの定義に仕込まれ、呼び出し前から存在します。ツールの出力経由のインジェクションは、メールやIssueなどのデータとして呼び出し時に届きます。定義のスキャンは前者には効きますが後者には効かないため、両方に封じ込めの対策が必要です。
MCPのtool poisoningは完全に防げますか?
防げません。MCPToxベンチマークでは攻撃成功率が最大72.8%に達し、最も高い拒否率でも3%未満でした。多くの毒入り定義は検知でき、スコープの制限、人の承認、Egressの許可リスト、ログで被害を小さくできます。
毒入りのMCPツールはどうやって見つけますか?
OWASP MCP03が勧めるとおり、ツール名・説明文・パラメーター説明に、モデル向けの命令文、不可視文字、コメント、機密パスがないかをスキャンします。そのうえで、承認した定義のハッシュを固定し、変化があればアラートを出します。
readOnlyHintなどのツールアノテーションは信頼できますか?
初期状態では信頼できません。2026-07-28版の仕様は、信頼できるサーバー以外のツールアノテーションは信頼できないものとして扱わなければならない(MUST)と定めています。「読み取り専用」と書かれているだけで自動承認しないでください。
MCPの仕様は、ツールを呼ぶ前に確認することを義務づけていますか?
MUSTではなくSHOULDです。仕様は、ツール呼び出しを拒否できる人を常に介在させるべきで、サーバーを呼ぶ前にツールの入力をユーザーに見せるべきだとしています。少なくとも外部送信と破壊的な操作には適用しましょう。
AIエージェントの「lethal trifecta(致命的な三要素)」とは?
Simon Willison氏の用語で、機密データへのアクセス、信頼できないコンテンツへの接触、外部と通信する手段の3つを併せ持つエージェントのことです。3つがそろうと、プロンプトインジェクションでデータが漏れ得ます。セッションごとに1つを外すか、人の承認で関門を設けます。
MCPサーバーのtool poisoningをスキャンできるツールは?
SnykのAgent Scan(旧mcp-scan)は、インストール済みのMCPサーバーについて、プロンプトインジェクション、tool poisoning、ツールシャドーイング、toxic flowsをスキャンします。ツールの説明文は同社の解析APIに送られます。この記事のスクリプトのような、ハッシュとスキャンを組み合わせた自前のチェックをCIで回す方法もあります。
毒入りやラグプルされたMCPサーバーを見つけたらどうすればいい?
まずそのサーバーを無効にし、問題のない最後のバージョンに戻して、そのセッションから届き得た認証情報をローテーションします。次に、影響があった期間に呼ばれたツールと外部の通信先をリクエストログで確認し、そのサーバーの定義をベースラインのレビュー対象に加えます。
まとめ
MCPのtool poisoningは、ツールの定義や出力を「指示」に変えてしまう攻撃で、現在のモデルはそれに従ってしまうことが多く、モデルの判断を防御の柱にはできません。ツール定義はコードと同じように扱い、ハッシュで固定し、スキャンし、設定ファイルやパッケージのバージョンも含めて変更のたびにレビューします。そのうえで、エージェントごとにスコープを絞った資格情報、致命的な三要素をそろえないセッション設計、外部送信と破壊的な操作の承認、Egressの許可リスト、サーバーの分離、呼び出しごとのログで被害を封じ込めます。脅威モデル全体はMCPセキュリティ対策チェックリスト25項目を参照してください。
参考資料(一次情報)
- MCP Specification 2026-07-28: Tools
- MCP Specification 2026-07-28: Security Best Practices
- OWASP MCP Top 10
- OWASP MCP03:2025 Tool Poisoning
- OWASP MCP06:2025(Intent Flow Subversion)
- Invariant Labs: MCP Security Notification — Tool Poisoning Attacks
- MCPTox: A Benchmark for Tool Poisoning Attack on Real-World MCP Servers(arXiv 2508.14925)
- NVD: CVE-2025-54136
- NVD: CVE-2025-54135
- Microsoft MSRC: How Microsoft defends against indirect prompt injection attacks
- Simon Willison: The lethal trifecta for AI agents
- Cloud Security Alliance: MCP Tool Poisoning and Rug Pull Attacks(2026年5月)
- Cloud Security Alliance: MCP Tool Poisoning and IDE Auto-Execution(2026年7月)
- Snyk Agent Scan(GitHub)
- NodeFlare Docs
