MCPツールサーバーにおけるセキュリティギャップ(そしてそれを直すために私が作ったもの)

Dev.to / 2026/3/26

💬 オピニオンDeveloper Stack & InfrastructureIdeas & Deep AnalysisTools & Practical Usage

要点

  • この記事では、MCP(Model Context Protocol)にはツールサーバー向けの組み込みセキュリティモデルがなく、認証、認可、監査などが欠けていると主張している。
  • 複数の社内サービスをMCPで公開する際に起きがちな失敗パターンとして、スコープのないフルアクセス、平文のAPIキーの乱立、ログの欠如、読み取り/書き込みの権限区別がないことなどを説明している。
  • ツールの合成(コンポジション)によって生じるリスクにも注目しており、個々のツールが想定していなかった形で、AIエージェントがサーバーをまたいで処理を連鎖させてしまう可能性がある。
  • 著者は、YAML設定とMCPの間に位置するポリシーおよび信頼のランタイムであるHeddleを構築したことを述べており、ツール呼び出しに対して権限を検証・強制する。
  • 開発時の逸話として、意図した「読み取り専用」の信頼ティアの下で行われたHTTP POST呼び出しをHeddleがブロックし、明示的な信頼のアップグレード、または設定の修正を要求した事例を紹介している。

MCPにはセキュリティモデルがありません。私はHeddleを構築しました。これは、YAML設定を検証済みで、ポリシーが強制されたMCPツールサーバーへと変換するポリシー/信頼レイヤーです。

MCP(Model Context Protocol)は、AIエージェントがツールに接続する方法です。Claude Desktopがそれを使い、Cursorもそれを使っており、何千人もの開発者がMCPサーバーを作って、AIに自分たちのAPI、データベース、インフラへのアクセスを可能にしています。

しかし、ひとつ問題があります:MCPにはセキュリティモデルがありません。

プロトコルは、クライアントがサーバーとどうやり取りするかを定めていますが、サーバーに何を許可するかについては何も述べていません。クライアントとサーバーの間の認証がありません。呼び出せるツールの権限管理がありません。何が起きたかの監査証跡がありません。仕様は、それらをあなた自身がすべて処理することを前提にしています。

ほとんどの人は、そうしません。

実際に何がうまくいかないのか

私はPrometheus、Grafana、Ollama、Gitea、そして他にもいくつかのサービスを使ったセルフホスト型サーバーを運用しています。Claude Desktopに、それらすべてをMCP経由で問い合わせさせたかったのです。標準的なアプローチは、それぞれに対してPythonのFastMCPサーバーを書くことです。サービスごとに数十行、APIキーをハードコードしてツールを登録して終わり、というものです。

ですが、実際に作ってしまったものを考えると、そのままでは済みません:

すべてのMCPサーバーは、そのプロセスが到達できるものなら何でも完全にアクセスできます。 Prometheusのツールは、GrafanaのAPIやGiteaのAPI、そしてlocalhost上の他の何にでもヒットできます。スコープ(範囲)がありません。

APIキーは環境変数や設定ファイルに置かれます。 もしMCPサーバーが9個あるなら、平文の認証情報が置かれる場所が9つあり、それに対するアクセスポリシーがありません。

何もログに記録されません。 Claudeがツールを呼んでサービスを再起動したりデータを削除したとしても、どのツールが、どんなパラメータで、どのエージェントによって、いつ呼ばれたのかの記録はありません。

読み取り専用と書き込みの区別がありません。 ツールは「存在する」か「存在しない」かのどちらかです。MCPは、query_prometheusは自由に呼んでよいが、restart_serviceは承認が必要だ、ということを知りません。

ツールの合成によって、思わぬリスクが生まれます。 Claudeが複数のMCPサーバーにアクセスできる場合、それらをまたいで呼び出しをチェーンできます。サーバーAが機密データを読み取り、サーバーBが外部APIへ投稿する――Claudeは、その組み合わせを、どちらのサーバーも想定していなかった形で行える可能性があります。

これらは机上の空論ではありません。開発中、あるエージェントを読み取り専用(Trust Tier 1)として宣言しましたが、HTTP POSTを使うツールを渡してしまいました。私が作ったシステムはそれを検知し、呼び出しをブロックして信頼違反をログに記録し、設定を修正するか、明示的に信頼レベルをアップグレードすることを強制しました。もしこの強制がなければ、そのツールは黙って動いてしまい、自分のセキュリティモデルが間違っていることに私は気付けなかったでしょう。

私が作ったもの

Heddleは、あなたのYAML設定とMCPプロトコルの間に入るランタイムです。ツールを設定ファイルで定義し、Heddleがそれらを検証し、安全にし、提供します。そして、すべての呼び出しに対してポリシーを強制します。

これはPrometheus用の完全なツールサーバーです:

agent:
  name: prometheus-bridge
  version: "1.0.0"
  exposes:
    - name: query_prometheus
      access: read
      description: "Run a PromQL query"
      parameters:
        query: { type: string, required: true }
    - name: get_alerts
      access: read
      description: "List active Prometheus alerts"
  http_bridge:
    - tool_name: query_prometheus
      method: GET
      url: "http://localhost:9090/api/v1/query"
      query_params: { query: query}
    - tool_name: get_alerts
      method: GET
      url: "http://localhost:9090/api/v1/alerts"
  runtime:
    trust_tier: 1

heddle run agents/prometheus-bridge.yamlを実行すると、Claudeは自然言語でPrometheusを問い合わせできるようになります。しかし、すべての呼び出しはAPIに到達する前に、6層のディスパッチパイプラインを通ります:

レート制限アクセスモードのチェックエスカレーションルール入力バリデーション信頼ティアの強制HTTPブリッジの実行

各レイヤーは独立して呼び出しをブロックし、その理由をログに記録できます。

セキュリティコントロール

ディスパッチパイプラインは、すべてのツール呼び出しに対してこれらのコントロールを強制します:

Trust Tiers(T1〜T4)。 各設定は信頼レベルを宣言します。T1(observer)はGETのみを使用できます。POST/PUT/DELETEは警告するだけではなく、実行時にブロックされます。T2(worker)はスコープ付きの書き込みを許可します。T3(operator)はエージェントをまたいだ呼び出しを許可します。T4(privileged)では人間の承認が必要です。私はこれで実際の誤設定を見つけました。T1エージェントがPOSTしようとしたところ、リクエストがプロセスの外へ出る前に強制機構がそれをブロックしたのです。

アクセスモードの注釈。 すべてのツールはaccess: readまたはaccess: writeとして宣言されます。書き込みツールを含むT1設定は、サーバーが起動する前に読み込み時点で拒否されます。これは、最小権限のためのスキーマレベル版です。

資格情報(クレデンシャル)ブローカー。 APIキーは~/.heddle/secrets.jsonに保存され、設定ごとのアクセスポリシーが付与されます。設定はそれらを{{secret:prometheus-token}}のように参照し、実行時に解決されます。YAMLファイルへ書き込まれることはありません。設定は、明示的に許可されたシークレットにしかアクセスできません。未承認のアクセスは拒否され、ログに記録され、実値ではなくプレースホルダーが返されます。

エスカレーションルール。 ツール呼び出しの実行ではなく、レビューのために保持する宣言的な条件。たとえば、私のVRAMオーケストレータには、モデル名に「27b」が含まれる場合は、いかなる smart_load 呼び出しも保持するというルールがあります。27ビリオンパラメータのモデルを読み込むと、私の24GB GPUメモリのほとんどを消費するためです。ルールが発火すると、呼び出しは保持され、監査ログにその理由が記録されます。

escalation_rules:
  - name: large-model-load
    reason: "VRAM  大部分  消費する モデル  読み込む ため (24GB  VRAM"
    tool: "smart_load"
    param_contains:
      model_name: "27b"

入力バリデーション。 すべてのパラメータに対して型チェック、長さ制限、インジェクションパターン検出を行います。バリデータは、シェルインジェクション(; rm -rf /)、SQLインジェクション(' OR 1=1)、パストラバーサル(../../etc/passwd)、そしてLLMプロンプトインジェクション(ignore previous instructions)を検出します。厳格モードではこれらはブロックされます。寛容モードではログに記録され、処理を通過させます。

ハッシュチェーン型監査ログ。 すべてのツール呼び出し、信頼違反、資格情報アクセス、エスカレーションによる保持は、JSON Linesエントリとしてログに記録されます。各エントリには、直前のエントリのSHA-256ハッシュが含まれます。誰かがログエントリを改ざんまたは削除するとチェーンが切れ、検証が失敗します。

設定への署名。 すべてのYAML設定はHMAC-SHA256で署名されます。署名後に設定が変更された場合、実行時に改ざんが検出されます。AIによって生成された設定(Heddleの自然言語ジェネレータから生成されたもの)は、明示的に昇格されるまで、ステージングディレクトリで自動的に隔離されます。

実行するとどんな見た目か

私は現在、単一のMCP接続を通じて、9つの設定から合計46個のツールをClaude Desktopに対して実行しています。設定は、Prometheus、Grafana、Ollama、Gitea、RSSアグリゲータ、RAG検索API、GPU VRAMオーケストレータ、日次の運用ブリーフィングエージェントをカバーしています。

これら46個すべてのツールは、同じディスパッチ処理パイプラインを通ります。PrometheusのツールはT1(読み取り専用、5つのツール)です。OllamaブリッジはT2(テキスト生成のためにPOSTできる)です。VRAMオーケストレータはT3(他のエージェントを呼び出せる。破壊的操作に対するエスカレーションルールを持つ)です。

信頼ティアはラベルではなく、強制されています。T1の設定は、HTTPブリッジURLが正しく、APIがそれを受け入れるとしても、物理的にPOSTリクエストを作成できません。強制側(enforcer)が、リクエストが構築される前にブロックします。

フレームワーク対応付け

すべてのセキュリティ制御は、少なくとも1つの業界フレームワークに対応付けられます。これは、コンプライアンスを示す必要がある組織にいる場合、または適用されたセキュリティアーキテクチャを示すポートフォリオを構築している場合に重要です(そのために私はこれを作りました)。

制御 OWASP Agentic Top 10 NIST AI RMF
信頼ティア #3 Excessive Agency GV-1.3
資格情報ブローカー #7 Unsafe Credential Mgmt MAP-3.4
監査ログ #9 Insufficient Logging MS-2.6
入力バリデーション #1 Prompt Injection MS-2.5
設定への署名 #8 Supply Chain GV-6.1
エスカレーションルール #3 Excessive Agency GV-1.3

脅威モデル全体(8つの脅威カテゴリ)は、リポジトリのdocs/threat-model.mdにあります。

はじめに

git clone https://github.com/goweft/heddle.git
cd heddle
python -m venv venv && source venv/bin/activate
pip install -e ".[dev]"

# スターターパックを試す
cp packs/prometheus.yaml agents/
heddle validate agents/prometheus.yaml
heddle run agents/prometheus.yaml --port 8200

Heddleには6つのスターターパックが同梱されています。Prometheus、Grafana、Gitea/GitHub、Ollama、Sonarr、Radarrです。agents/にそのまま投入して、すぐに実行できます。Ollama(テキスト生成のためのT2)以外はすべて読み取り専用(T1)です。

または、自然言語から設定を生成できます。

heddle generate "Home Assistant APIをラップするエージェント" --model qwen3:14b

Claude Desktop、Cursor、そしてstdioトランスポートをサポートする任意のMCPクライアントで動作します。

Heddlegithub.com/goweft/heddleでMITライセンスのオープンソースです。126のテスト、15のセキュリティ制御、そしてOWASP Agentic Top 10とNIST AI RMFに対応付けられた脅威モデルがあります。AIエージェントに対してAPIを公開する場合、どのセキュリティ制御が存在していてほしいか、ぜひ教えてください。