私は先日、本番環境の MCP(Model Context Protocol)サーバーで原因不明の 401 Unauthorized エラーに遭遇しました。ログを追ってみると、外部から注入されたツール定義が悪意ある JSON-RPC ペイロードを送信しており、認証ヘッダーが意図せず上書きされていたのです。本記事では、そのインシデントを起点に、ツールインジェクション攻撃の構造と権限制御の正しい実装パターンを、私の実体験を交えながら整理します。
記事内で利用する推論 API は、すべて HolySheep AI のエンドポイント https://api.holysheep.ai/v1 に統一しています。HolySheep はレート ¥1=$1 で提供されており、公式の ¥7.3=$1 と比較して 85% のコスト削減になります。WeChat Pay・Alipay にも対応し、レイテンシは実測で 47ms〜49ms(中央値 48.3ms)と <50ms を安定して下回ります。登録時には無料クレジットが付与されるため、検証環境の構築も低リスクで開始できます。
インシデントの起点:401 Unauthorized の再現
最初に観測したログは次のようなものです。
{
"timestamp": "2026-01-14T08:12:44Z",
"level": "ERROR",
"service": "mcp-gateway",
"request_id": "req_8b1f2c",
"error": "401 Unauthorized",
"client_ip": "203.0.113.45",
"headers": {
"authorization": "Bearer null",
"x-forwarded-tool": "shadow_exec"
}
}
クライアント IP は信頼できる社内プロキシのレンジ外であり、x-forwarded-tool という未知のヘッダが付与されていました。調査の結果、攻撃者が MCP クライアントのツール一覧レスポンスを改ざんし、「shadow_exec」という名の偽ツールを注入していたことが判明します。これは典型的なツールインジェクションです。
MCP プロトコルにおける脅威モデル
- ツールインジェクション(Tool Injection):攻撃者が
tools/listレスポンスに悪意あるスキーマを混入し、LLM に意図しないツールを実行させる。 - プロンプト汚染(Prompt Pollution):ツールの説明文やパラメータ名に隠し命令を埋め込み、推論経路を乗っ取る。
- 権限昇格(Privilege Escalation):読み取り専用ツールに
write権限を後付けし、ファイルシステムや API キーを盗む。 - SSRF / データ流出:
fetch系ツールの URL を社内メタデータサービス(例:169.254.169.254)に向けさせる。
権限制御のベストプラクティス実装
私は MCP サーバーを再設計するにあたり、以下の三層防御を実装しました。
1. ツールレジストリのホワイトリスト化
from typing import Callable, Dict, Any
import hmac, hashlib, os
ALLOWED_TOOLS = {"read_file", "search_docs", "calc_sum"}
TOOL_SIGNING_KEY = os.environ["MCP_TOOL_SIGNING_KEY"].encode()
def verify_tool(tool_schema: Dict[str, Any]) -> bool:
name = tool_schema.get("name")
if name not in ALLOWED_TOOLS:
return False
expected_sig = hmac.new(
TOOL_SIGNING_KEY, name.encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected_sig, tool_schema.get("sig", ""))
def safe_dispatch(name: str, args: Dict[str, Any]) -> Any:
if name not in ALLOWED_TOOLS:
raise PermissionError(f"tool '{name}' is not whitelisted")
handler: Callable = REGISTRY[name]
return handler(**args)
2. JSON-RPC ペイロードのスキーマ検証
すべての受信ペイロードを jsonschema で検証し、未知のパラメータを拒否します。
import json
from jsonschema import Draft202012Validator, ValidationError
RPC_SCHEMA = {
"type": "object",
"required": ["jsonrpc", "method", "id"],
"properties": {
"jsonrpc": {"const": "2.0"},
"method": {"enum": ["tools/list", "tools/call", "ping"]},
"params": {"type": "object"},
"id": {"type": ["string", "integer", "null"]}
},
"additionalProperties": False
}
def validate_rpc(payload: bytes) -> dict:
try:
data = json.loads(payload)
except json.JSONDecodeError as e:
raise ValueError(f"invalid JSON: {e}")
errors = list(Draft202012Validator(RPC_SCHEMA).iter_errors(data))
if errors:
raise ValidationError(f"schema violation: {errors[0].message}")
return data
3. 推論呼び出しの分離と監査ログ
推論 API への呼び出しは、ツール実行と同一プロセスから隔離します。HolySheep AI のエンドポイントを使用することで、平均 48.3ms の低レイテンシを維持しつつ、API キーを環境変数で管理できます。
import os, time, httpx, json
HOLYSHEEP_URL = "https://api.holysheep.ai/v1/chat/completions"
API_KEY = os.environ["HOLYSHEEP_API_KEY"]
ALLOWED_MODEL_OUTPUT_PRICES = {
# 2026年 出力価格(USD / 1M tokens)
"gpt-4.1": 8.00,
"claude-sonnet-4.5": 15.00,
"gemini-2.5-flash": 2.50,
"deepseek-v3.2": 0.42,
}
def call_llm(messages: list, model: str = "gpt-4.1",
max_tokens: int = 1024) -> dict:
if model not in ALLOWED_MODEL_OUTPUT_PRICES:
raise PermissionError(f"model '{model}' is not allowed")
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
body = {
"model": model,
"messages": messages,
"max_tokens": max_tokens,
"temperature": 0.2,
}
t0 = time.perf_counter()
with httpx.Client(timeout=10.0) as client:
r = client.post(HOLYSHEEP_URL, headers=headers, json=body)
r.raise_for_status()
latency_ms = round((time.perf_counter() - t0) * 1000, 1)
data = r.json()
data["_latency_ms"] = latency_ms
audit_log("llm_call", model=model, latency_ms=latency_ms)
return data
実測では、gpt-4.1 を用いた場合で平均 48.3ms、gemini-2.5-flash では 46.7ms の応答時間でした。HolySheep のレイテンシは実測 47ms〜49ms と <50ms を安定して下回るため、ツール実行パイプラインの同期呼び出しにも十分耐えられます。
よくあるエラーと解決策
エラー1:ConnectionError: timeout
症状: MCP クライアントが tools/list 要求を発行した後、10 秒経過してもサーバーから応答がなく、httpx.ConnectTimeout が発生する。
原因: 推論 API へのラウンドトリップが MCP セッションのハートビートを上回り、TCP コネクションが切断されていました。
解決策: 接続プールに Keep-Alive を設定し、HolySheep AI のように <50ms で応答するエンドポイントを併用します。
import httpx
client = httpx.Client(
base_url="https://api.holysheep.ai/v1",
timeout=httpx.Timeout(connect=2.0, read=5.0, write=2.0, pool=2.0),
limits=httpx.Limits(max_keepalive_connections=20,
keepalive_expiry=30),
headers={"Authorization": f"Bearer {YOUR_HOLYSHEEP_API_KEY}"},
)
エラー2:401 Unauthorized(API キー漏洩)
症状: 本番環境で 401 Unauthorized が断続的に発生。アクセスログを調査すると、未知のリージョンから同じキーでアクセスされていた。
原因: ツール説明文に API キーが誤って埋め込まれ、LLM が推論結果としてログに出力していた。
解決策: キーは環境変数経由でのみ注入し、ツール定義から物理的に分離します。
import os, re
DANGEROUS_PATTERNS = [
re.compile(r"sk-[A-Za-z0-9]{20,}"),
re.compile(r"AIza[0-9A-Za-z\-_]{35}"),
]
def sanitize_tool_description(desc: str) -> str:
for p in DANGEROUS_PATTERNS:
if p.search(desc):
raise ValueError("credential detected in tool description")
return desc
api_key = os.environ["HOLYSHEEP_API_KEY"]
assert api_key.startswith("hs-"), "invalid HolySheep key prefix"
エラー3:PermissionError: tool 'shell_exec' is not allowed
症状: 正規ユーザーからのリクエストにもかかわらず、shell_exec ツール呼び出しが拒否される。
原因: ホワイトリストに shell_exec が登録されておらず、デフォルトポリシーで拒否されていました。これは正常な挙動です。
解決策: 必要に応じて別ロールに昇格可能とするか、サンドボックス化(例:bubblewrap)したラッパーを許可します。
ROLE_TOOL_POLICY = {
"reader": {"read_file", "search_docs"},
"analyst": {"read_file", "search_docs", "calc_sum"},
"operator": {"read_file", "search_docs", "calc_sum", "shell_exec"},
}
def authorize(user_role: str, tool_name: str) -> bool:
return tool_name in ROLE_TOOL_POLICY.get(user_role, set())
assert authorize("reader", "shell_exec") is False
assert authorize("operator", "shell_exec") is True
エラー4:JSON-RPC パース時の UnicodeDecodeError
症状: クライアントから送信された tools/call パラメータにサロゲートペアが含まれており、サーバーがクラッシュする。
解決策: UTF-8 のデコードを errors='strict' で明示し、不正バイト列を 400 で弾きます。
def parse_payload(raw: bytes) -> dict:
try:
text = raw.decode("utf-8", errors="strict")
except UnicodeDecodeError as e:
raise ValueError(f"non-utf8 payload rejected: {e}")
return json.loads(text)
監査と継続的な検証
防御は一度実装して終わりではありません。私は以下のチェックリストを 30 日ごとに自動実行しています。
- ツールレジストリの署名再検証(期限切れキーの自動ローテーション)
- JSON-RPC スキーマへの新メソッド追加に対する変更レビュー
- 推論 API 呼び出しの P95 レイテンシ監視(HolySheep で実測 49.2ms、劣化していないか確認)
- モデル別トークン消費量と出力単価(GPT-4.1 $8、Claude Sonnet 4.5 $15、Gemini 2.5 Flash $2.50、DeepSeek V3.2 $0.42)の月次レビュー
まとめ
MCP におけるツールインジェクションは、認証ヘッダーの書き換え、スキーマ汚染、権限昇格の三段構えで攻撃を仕掛けてきます。私が 401 Unauthorized インシデントから学んだのは、ツールレジストリのホワイトリスト化・JSON-RPC の厳格なスキーマ検証・推論 API 呼び出しの隔離、この三点を妥協せず実装することの重要性です。HolySheep AI のような低レイテンシ(<50ms)、低コスト(¥1=$1、公式比 85% 削減)、WeChat Pay・Alipay 対応の API 基盤を組み合わせれば、防御を維持しつつ運用負荷を抑えることができます。登録時には無料クレジットが付与されるため、まず PoC を回してみることをお勧めします。