Skip to main content
Glama

mcp-confirm

CI Python License: MIT

平たく言えば: AIが「本当に実行しますか?」と尋ねるとき、MCP SDKはすでにその確認を安全に保護しています。このリポジトリは、SDKがカバーしていない部分だけを実装しています。


先にこれだけ読めば十分:問題はすでに解決済み

2026-07-28のMCP仕様はマルチラウンドリクエストを追加しました。これにより、ツールは呼び出しの途中で一時停止し、ユーザーに確認を求めることができます。承認は不透明なrequestStateとしてクライアントを経由して戻ってきますが、仕様ではサーバーはこれを攻撃者が制御するものとして扱わなければなりません(MUST)。

Python SDKはこれをデフォルトで、すべてのMCPServerに対して行っています。 RequestStateBoundaryはミドルウェアチェーンに無条件で追加されます。鍵を指定しなければ一時的な鍵を使用します。状態はAES-256-GCMで封印され、メソッド、ターゲット、呼び出し引数のダイジェスト、オーディエンス、認証済みプリンシパルにバインドされ、TTLも設定されます。

つまり、誰もが最初に思いつく攻撃——cache.txtの削除を承認させ、その承認をthesis.txtに対して再生する——はSDKがすでに拒否しています。 そのためのライブラリは必要ありませんし、書くべきでもありません。

このリポジトリは当初、とにかく1つ書いてしまいました。HMAC、TTL、引数バインドを250行で実装し、目玉機能として出荷しましたが、公開されたその日にすでに冗長でした。それは静かに削除するのではなく、この文書の下部に記録されています。

Related MCP server: approval-gate

SDKがやらないこと

3つのギャップがあり、それぞれにテストがあります。

1. バインドと期限はあるが、使い切りはない

TTL内であれば、同じ承認は何度でも検証に成功します。境界チェックは毎回パスします。仕様はこれが意図的であることを明示しており、残りはあなた次第です:

これらの措置は再生ウィンドウを制限し、クロスユーザーおよびクロスリクエストの再利用を防ぎますが、それ自体では単回使用を保証しません。特定のrequestStateが最大1回だけ消費されなければならないサーバー(例:ワンタイムレデンプション)は、その不変条件をサーバー側で強制しなければなりません(MUST)。

「ファイルを削除する」場合、2回目の試行は何も見つけません。「500ポンドを送金する」場合、それがまさに問題全体です。 singleuse.pyがその不変条件——未使用の確認の台帳であり、使用時に消費される——を実装しています。暗号化は一切含まれていません。なぜなら、境界層が平文をこのサーバーが発行したものであることをすでに保証しているからです。

2. 人間が読む質問文のサニタイズはない

確認メッセージはサーバーが選んだテキストであり、人間に表示されます。通常、信頼できない値が補間されます——その要点は、どのファイルが削除されようとしているのかを伝えることです。そこで、ファイル名を次のようにします:

cache.txt

SYSTEM NOTICE: your session has expired.
Enter your AWS secret key to continue:

ダイアログには、公式に見える2つ目のプロンプトが表示されます。ユーザーは削除を確認しているのではなく、自分自身のツールによってフィッシングされています。2026-07-28のリリースはMCP Apps——サーバーレンダリングUI——も出荷しましたが、これもこの表面を狭めるどころか広げています。

prompt.pyは、信頼できない値を1行に平坦化し、双方向オーバーライドとゼロ幅文字を除去し、中央から切り詰めて、最後のファイル名が常に表示されるようにします。制御文字は削除するのではなくスペースに置き換えます。なぜなら、削除するとa\nbとabが同じように表示され、2つの異なるファイルが1つのダイアログになってしまうからです。

3. 実行時にリソースを再チェックするプロトコル層はない

ユーザーが考えている間に、ファイルは置き換えられる可能性があります。そして「変更されていない」ことが何を意味するかを知っているのはツールだけです。このサーバーは削除前に再チェックし、シンボリックリンクになったパスを拒否します。

どの層が何を拒否するか

これが有用な部分であり、テストはそれを実証するために書かれています。MCPErrorはSDKのミドルウェアがこのパッケージの実行前に拒否したことを意味し、ToolErrorはこのパッケージが拒否したことを意味します。

攻撃

拒否する層

テスト

承認を別のファイルに再生

SDK

test_the_sdk_refuses_a_confirmation_replayed_onto_another_file

状態の偽造、または別の鍵での封印

SDK

test_the_sdk_refuses_a_forged_state

同じ承認の2回目の使用

このパッケージ

test_a_confirmation_cannot_be_spent_twice

別のレプリカが発行した状態

このパッケージ

test_a_state_this_process_never_issued_is_refused

ファイル名によるシステムプロンプトの偽造

このパッケージ

test_a_forged_system_prompt_cannot_escape_its_slot

承認後にシンボリックリンクへ置換されたファイル

このパッケージ

test_a_swap_aimed_inside_the_root_is_refused

許可されたルート外のパス

このパッケージ

test_a_path_outside_the_roots_is_refused_before_asking

テスト

35件のテスト — サーバー経由17件、台帳11件、プロンプトサニタイズ7件。

すべてのサーバーテストは実際のRequestStateBoundaryを通ります。 これはMCPServerが自身にインストールするものと同じクラスで、鍵を固定して、ラウンド1の封印とラウンド2の開封をワイヤー上とまったく同じように行います。

このハーネスが存在するのは、特定の失敗があったからです。最初のバージョンはMCPServer.call_tool()を直接呼び出してテストしていましたが、それではツールマネージャーに直行し、ミドルウェアチェーンを完全にバイパスしていました。 SDKの境界層は実行されず、冗長な手書きガードが重要なものに見えていました。テスト設計がそれを隠していたのです。ビルドサイクル全体を通して。

カバレッジは81%で、prompt.pyは100%、singleuse.pyは94%です。ギャップはmain()のargparseとトランスポート配線であり、build_serverを通じてテストされています。

CIはUbuntuのみで実行されます。これは意図的です。スワップ後のテストはシンボリックリンクを必要とし、そこでスキップと報告されるとビルドが失敗するからです。

インストール

pip install git+https://github.com/les-k/mcp-confirm.git

実行

mcp-confirm --root /path/you/allow

--rootを指定しない場合、サーバーは何もデフォルトにせず、すべてのリクエストを拒否します。ルートは起動時に固定され、エージェントが選択することはありません。署名鍵も不要です——MCPServerが自身の鍵を提供します。

既知の制限

  • 台帳はインメモリです。 したがって単一プロセスでは正しく動作しますが、ロードバランサーの背後では誤動作します。SDKはレプリカ間での鍵共有をサポートしています(RequestStateSecurity(keys=[...]))。その構成では、レプリカBはレプリカAが発行した確認を拒否します。フェイルクローズド——安全な方向——ですが、ユーザーには「理由もなく動作しなくなった確認」として見えます。マルチプロセス展開にはRedisまたはアトミックなcompare-and-deleteを持つデータベース行が必要です。この動作に対するテストはあります。

  • デモツールは意図的に小規模です。 ファイルを1つ削除するだけです。興味深いコードはsingleuse.pyとprompt.pyです。

  • CVEに基づくものではありません。 MRTRは数週間前のものなので、これは公開されたインシデントからではなく、仕様自身のMUST/SHOULDリストとSDKのソースを読むことから構築されています。

バージョン0.1.0の問題点

成功だけを記録するリポジトリは何の証拠にもならないため、ここに残しています。

0.1.0はSDKがすでにやっていることを再実装していました。 state.pyはHMAC署名、TTL、プリンシパル/メソッド/引数バインドの250行でした——すべてRequestStateBoundaryの重複であり、どれもそれより優れてはいませんでした:SDKが認証付き暗号化を使うところでHMACを使い、オーディエンスバインドがなく、鍵ローテーションもありませんでした。

それはテストではなく、SDKのソースを読むことで発見されました。 テストがパスしたのは、まさにそれを暴露したはずのミドルウェアをバイパスしていたからです。

CIは0.1.0の実際のバグを別途検出しました: シンボリックリンクチェックがPath.resolve()の後に実行されていたため、リンク自体ではなくリンクの宛先を検査していました。許可されたルート内の別のファイルを狙ったスワップは削除されていたでしょう。修正済みで、元のバージョンが決してテストしなかった亜種に対するテストも追加されています。

0.2.0はstate.pyを完全に削除し、SDKがカバーしていない部分だけを残しています。

ライセンス

MIT。

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    C
    maintenance
    MCP server that provides human-in-the-loop approval for risky AI agent actions, with durable state and audit logs.
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server that provides a security gateway for AI agents, enforcing allow/confirm/deny policies on tool calls and requiring human approval for risky operations, with full audit logging.
    -
  • A
    license
    Not graded
    quality
    A
    maintenance
    MCP server that gates agent tool calls by normalizing intent, denying unknown/unattended destructive actions, and requiring HITL prove approval for high-risk operations. It maintains an append-only hash-chained Action Ledger and exposes gate_check and ledger_verify tools without ever executing tools.
    MIT