关于 Clay Seal
Clay Seal 是面向需要约束 AI 工具使用的团队的策略网关,会在整个会话范围内评估授权、累计预算、参数来源和既往操作,再允许、暂缓或拒绝调用。
主要功能
- 01会话级策略检查
- 02调用与金额累计预算
- 03允许、升级审批或拒绝
- 04按策略过滤工具目录
- 05Python 包装器与 MCP 代理
适用场景
- 01限制多次退款的累计金额
- 02要求审批名单外的邮件
- 03限制 MCP 服务公开的工具
MCP 相关
Clay Seal 可作为 MCP 网关,置于客户端与现有服务之间。它按会话策略检查工具调用,并从目录中移除未授权工具。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
Clay Seal
将业务流程逻辑约束纳入 AI 系统。
你的 Agent 退款上限为 $1,000,却发出 11 笔 $900 的退款。每笔退款都没有超过单笔上限,因此每次调用检查都能通过,最终退款总额达到 $9,900。任何一次只检查单笔调用的机制都无法发现这一点。
Clay Seal 位于你的工具前方,会结合整个会话来判断每次调用:授权、累计总额、参数来源,以及 Agent 已经执行的操作。
目录
| 从这里开始 | 安装并观察它阻止两种攻击 |
| 两行即可使用 | 封装你已有的工具 |
| 部署在 MCP 服务前 | 无需更改代码 |
| 编写策略 | 操作员签署的授权 |
| 工作原理 | 按执行顺序排列的六层机制 |
| 衡量什么 | 指标及其不代表的含义 |
| 边界在哪里 | 它不会做什么 |
| 文档 | 先看 docs/START.md,然后看 docs/ |
| 从源代码构建 | 并运行测试 |
正在开发代码? docs/INTEGRATION.md 列出了已发布的网关实际执行的模块,这是区分实时执行路径与未接入部分的最快方式。决策逻辑位于 clayseal/capabilities/broker.py 中的 SessionBroker._authorize_locked;大多数拒绝由同一文件中的 _floor 触发。
从这里开始
如果你是编码 Agent,跳转到部署操作手册。
pip install clayseal
clayseal try
clayseal try 大约需要一分钟。它会在你面前运行两种攻击,并展示网关如何阻止它们。无需配置、无需获取密钥,也不会有任何内容离开你的设备。
图中的每个判定都由与你实际部署时相同的网关实时作出。图片由 python scripts/render_try_svg.py 根据真实运行生成;如果图片与命令输出不一致,测试就会失败,因此它不会变成一张展示过时功能的截图。
支持 Python 3.10 至 3.14。依赖项有两个:cryptography 和 pyyaml。
关于名称。 目录名为
agentauth-capabilities,包名为clayseal,导入路径为clayseal.capabilities。该项目在 0.6 版本时更名;agentauth仍可通过弃用兼容层导入。详情请参阅文末的 命名。
如果你是 Agent
按顺序在你正在编辑的项目中执行:
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(新实例会重置限额)。捕获 Refused
(预算不足 / 未授权)和 StepUpRequired(已暂缓 — 通常用于非允许列表中的
电子邮件)。非文件工具放在 paths.pathless 中。
如果已经是 MCP 服务(Claude Desktop / Cursor):使用 clayseal proxy,而不是
clayseal serve。不要让 proxy 指向普通的 .py 文件。clayseal howto
是操作手册。
两行即可上手
包装你已有的工具。Agent 的其他部分无需更改。
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)
第一次退款会通过。第二次会被拒绝,因为 $1,000 的 会话上限已经用完。两次调用都没有到达你的函数。
这里直接内联了策略,因此你可以将全部内容粘贴到文件中并运行。
在实际部署中,策略单独存放在 YAML 文件中,安全团队可以审核,
并且可以通过拉取请求进行把关。clayseal policy new > policy.yaml
会生成一份带注释的模板供你开始使用,Guardrail.from_policy_file 会加载它。
包装器会保留原函数的名称、文档字符串和签名,因此任何会检查函数信息的 框架看到的工具都与之前相同。这适用于 LangGraph、OpenAI Agents SDK、CrewAI 和手写循环。像调用原函数那样调用 包装器,可以使用位置参数或按名称传参。
或将其置于 MCP 服务前面
完全无需更改代码。网关使用 MCP 协议,因此它位于 Agent 与 服务之间:
clayseal proxy --policy policy.yaml -- npx @your-org/mcp-server
也可以将此内容粘贴到 Claude Desktop 的 MCP 配置、Cursor 的 MCP 设置,或
.cursor/mcp.json 中。Agent 与 Clay Seal 通信;Clay Seal 与服务通信:
{
"mcpServers": {
"billing": {
"command": "clayseal",
"args": ["proxy", "--policy", "policy.yaml",
"--", "npx", "@your-org/mcp-server"]
}
}
}
策略未授权的工具会从目录中移除,因此 Agent 根本不会知道它们的存在。
编写策略
这是一份完整策略,通过 lint 检查。
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。它会发现最关键的错误:
能够花钱的工具却没有扣减预算。对于上面的文件,它会报告没有错误和一条警告
(除非绑定主体,否则上限按会话计算),这指出了一个你还没有做出的真实决策。
完整参考:docs/POLICY.md。
查看它如何阻止攻击
在代码仓库检出目录中运行。examples/ 中有七个示例,每个都无需密钥和 网络即可运行:
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 错误证明 Agent 收到了 拒绝;服务自身的账本证明退款并未发生,这是 两种不同的说法。相同的运行也可以在命令行上执行,这正是实际部署时 采用的方式:
clayseal proxy --policy examples/refund.yaml -- python examples/refund_server.py
工作原理
你向网关提供一个策略文件和本次会话的目标。网关会在会话开始时封存目标,因此,Agent 之后读取的任何内容都无法扩大已批准的范围。每次工具调用执行前,都会经过同一个决策点。
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
底部的循环就是核心思路。网关不只是检查一次调用,还会根据会话至今的全部操作来检查调用。
调用会得到以下三种结果之一:
- 允许,调用会转交给工具
- 升级审批,调用会等待人工批准
- 拒绝,调用不会执行
设置升级审批,是因为错误拒绝的代价很高。对于自主攻击者,等待批准的调用和被拒绝的调用一样能有效阻止攻击;而合法的 Agent 不会因此被永久阻断。只有存在问题的确凿证据时才会拒绝。
产生这些结果的检查会按固定顺序运行,从成本最低、限制最严格的检查开始,因此,较早被拒绝的调用不会进入成本更高的检查层。
- 底线规则。 一组简单规则:授权是否过期、是否允许使用此工具、此路径是否在范围内、此目的地是否在出站列表中、预算是否还有余额。这是最快的一层,也是大多数拒绝的来源。
- 声明检查。 如果 Agent 预先说明了计划,就会先将计划与封存的目标进行核对,再执行任何操作。
- 内容检查。 检查已声明的写入是否符合目标,并检查会产生影响的写入所携带的内容。会触发升级审批,但绝不直接拒绝。
- 会话状态。 检查累计总量、哪些值来自不可信文本,以及针对这些值设定的规则。
- 来源检查。 检查目的地的来源。封存目标中指定的收款方是可信的;之后 Agent 读取的文本中出现的收款方则不可信。
- 行为检查。 根据目标监测会话的行为模式。默认仅提供建议:它会升级处理,但不会阻止操作。它的拦截层级在算术上也不可能触发,除非已观察到足够多的正常会话来完成校准,因为 conformal p 值不能低于 1/(n+1),而它们所设的 alpha 阈值低于此下限。检测器会标明未启用的层级,因此,不会让无法触发的层级看起来像是经过检查但未发现问题。
下文中的预算是指网关在整个会话中累计的总量:可以是金额、调用次数,也可以是任何其他可计数的项目。这是唯一能够发现多个单独合法的调用累积后变得不合法的检查;下方的测量结果也表明,它决定了这种方法是否有效。
测量结果
场景和测试范围扫描脚本位于代码仓库中,而不在 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 个业务流程场景,每个场景都有一个脚本化攻击及其良性对应场景。结果分为两列,因为单独优化任何一列都很容易:拒绝所有操作即可赢得攻击遏制率,允许所有操作即可赢得任务完成率。真正重要的是两者同时满足。
在完整测试集中,攻击被遏制,且对应的良性场景也完成,n=132:
| 条件 | 遏制攻击且完成任务 |
|---|---|
| 未防护 | 0/132,97.5% 上界为 2.8% |
| 拒绝所有操作 | 0/132,97.5% 上界为 2.8% |
| 按策略进行逐次调用授权 | 0.8% (1/132) |
| 数据流污点分析 | 11.4% (15/132) |
| Clay Seal | 39.4% (52/132) |
相比数据流污点分析,提升了 28.0 个百分点 [18.2, 37.9],精确 McNemar p=1.2e-07。在 132 个良性对应场景中,此网关拒绝了 2 个,且都未造成工作损失;污点分析拒绝了 49 个,其中 43 个造成了工作损失。
标题之前要先看这个数字
39.4% 是两个不同情况的平均值,不是成功率。 决定你属于哪种情况的是规则;运行任何东西之前,先阅读自己的策略就能判断:
| 规则是否规定了可计数的限额? | 场景数 | Clay Seal | 数据流污点分析 |
|---|---|---|---|
| 是 | 42 | 83.3% [69.4%, 91.7%] | 2.4% [0.4%, 12.3%] |
| 否 | 90 | 18.9% [12.1%, 28.2%] | 15.6% [9.5%, 24.4%] |
Fisher 精确检验 p = 6.5e-11。
所以,请将规则写成对可计数事项设定上限。“每个会话的退款不超过 $1,000”可以强制执行。“不要做任何不妥的事”则不行:网关无法维护这类事项的累计值。clayseal policy lint 已会标记能花费资金却不扣减预算的工具,而这项测量说明了该警告的意义。
其余内容
上面的测试套件是我们编写的,这是首先应该怀疑的地方。 docs/EVIDENCE.md 全面列出了非我们所有的证据、网关的成本和局限:
| 了解防御机制的攻击者 | 24 组语料库与目标,共 173,699 个候选项。其中 23 组里,完全查看判定结果对攻击者毫无帮助。第 24 组则说明自适应攻击奏效时的价值 |
| 十一组外部语料库,均非由本项目作者编写 | 当危害由网关能够约束的事项定义时,成功率为 98% 到 100%;当危害由获授权操作的内容定义时,成功率为 31% 到 55%;另有摩擦成本列,说明策略不完整的代价 |
| 一个真实模型,AgentDojo 提示词注入 | 216 次运行中有 1 次攻击成功,成功率为 0.5% [0.1, 2.6];Progent 公布的成功率为 11.1% 到 16.7% |
| 相应成本,在四个模型上按任务配对测量 | 最弱模型下降 25 个百分点,最强模型下降 3 个百分点;CaMeL 公布的成本为 7 个百分点 |
| 每次判定耗时 34 µs,不随会话长度变化 | 以及比中位数更值得关注的三个数字 |
| 凌驾于其他所有指标之上的局限 | 对于无法预先知道下一步操作的开放式任务,每项任务会中断 21.67 次 |
每项主要结果及其复现命令: benchmarks/results/README.md。
边界所在
有三种放置强制执行点的方式。它们在 Agent 必须如何绕过才能通过方面有所不同,与其将其归为一个数字,不如直截了当地说明这种差异。
| tier | 中介对象 | 绕过方式 | 状态 |
|---|---|---|---|
| in-process | 由你的 harness 通过 authorize() 路由的工具调用 | 不调用它的 harness,或未封装的工具 | 已发布 |
| proxy | 按设计拦截传输上的每个工具调用 | 能通过其他方式访问该工具的进程 | 已发布 |
| syscall | 来宾能够尝试的一切,且不可伪造 | 此层不存在绕过方式 | 参考后端仅支持 macOS |
代理还会运行进程内路径无法执行的三项检查。它会检查调用中提及的每条路径,而不只是操作携带的那条路径。它会拒绝 JSON 中存在重复键的消息。它还会暂缓执行有副作用的调用,直到收到之前的结果,因此在获知目标来源之前,不会被要求判断其来源。 docs/POLICY.md 中的表格列出了这些差异,以及进程内集成必须自行完成的工作。
大多数部署都需要代理。Agent 连接到 clayseal proxy,而 clayseal proxy 会运行真正的 MCP 服务,因此被拒绝的调用会收到 JSON-RPC 错误,服务器子进程也不会收到该帧。策略之外的工具也会从公布的目录中移除,因此 Agent 不会围绕一个之后会被拒绝的工具制定计划。
syscall 层会将信封的出站流量范围和路径范围编译为沙箱策略,并返回一条不可伪造的判定流。参考后端由 iVisor 驱动。
它能捕获上层都无法捕获的一类行为。 DNS 隧道会将负载编码到子域名中,数据通过解析器流出。整个过程不涉及工具。没有工具调用,因此不会请求 authorize(),上层各层也都不会拒绝任何操作。在捕获的跟踪记录中,iVisor 拒绝了全部四个查询,而工具调用层一个也没有拒绝
(syscall_tier.md)。
反过来也一样。通过允许列表中的通道发送信标,iVisor 没有可拒绝的内容,因为每次 syscall 确实都获准执行;异常之处在于累计总量,只有会话预算能对此进行限制。两层互不取代,这正是同时运行两者的理由。
iVisor 从设计上仅支持 macOS:Hypervisor.framework 允许每个进程运行一个 VM,并会对调用方应用不可逆的 Seatbelt 配置文件。agentauth.sandbox_backends 入口点支持替换底层实现,而 Linux seccomp 或 Landlock 后端仍是待完成的工作,并非我们已发布的内容。
仍未弥合的缺口
范围内暂存。Agent 在授权范围内读取一个秘密级对象,并将其内容写入同一授权范围内的一个公开级对象。这两个操作都获准执行,而路径范围无法提供帮助,因为 Agent 获准使用的工作区确实同时包含这两个对象。以预言机攻击者衡量,泄露任务集合与 allow-all 完全相同
(in_scope_exfiltration.md)。机密性流跟踪器作为逐步升级的防护层覆盖了其中一部分;宽泛的片段拆分和无密钥编码仍未解决。对编码 Agent 的任何遏制能力声明都必须涵盖这一点。
策略文档
授权依据是一个文件,因此安全团队可以阅读它,在拉取请求中查看差异,并以此作为合并门禁。
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
编译后的策略包含对文档计算出的摘要,网关会将其附加到每项决策上,因此审计轨迹记录的不只是决策内容,还会注明作出该决策的授权依据。
指向你自己的工具
两项声明即可完成这项工作,两者之所以存在,都是因为默认值针对的是基准目录,而非真实目录。
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 会说明这能为哪些义务提供证据:映射准确时引用框架原文,不准确时则停留在功能层面。文档开篇便说明了它不是什么:库不是控制制度,也不能让任何人因此符合任何要求。
根据已有内容编写第一份策略
有两个命令可用,因此第一份策略不必从空白文件开始。两者都会生成由人完成的草稿,绝不会授予权限。
# 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 会从文字中提取额度、时间窗口、仅一次规则、目录和允许的域名,例如“单笔供应商付款不得超过 $10,000”。
两者都不是权威依据,原因各不相同。文档是由没有考虑 Agent 的人编辑的产物,因此如果由它决定授权,控制文档的人就能控制授权。目录由受此策略约束的服务编写,因此,如果服务将 wire_funds 描述为“读取余额”,它就等于在自行设定限制。由此得出三项特性:
- 每条规则都会注明其来源文档中的行号,审阅者无需重新阅读源文档,就能核对 YAML 与对应句子。
- 看起来像规则的句子如果未能转换,就会成为
TODO注释。看似完整的草稿不如坦承遗漏的草稿,因为转换过程中消失的规则无人会注意到。 - 目录可以提高工具的影响等级,但绝不能降低;如果服务将自己的有影响操作工具称为只读,系统会报告这一点,而不会采信。未发布架构的工具不会被记录为不需要路径: “服务未说明”与“服务说明了不需要”是两回事。
刻意留空的是 budgets.tracked,也就是额度与工具的关联处。文档知道额度,目录知道哪个参数承载金额,但哪个工具会扣减哪个额度,两者都没有说明;关联错误会把共享额度拆成两个额度。系统会将两者都打印为注释形式的建议,供人连接。
在一份 30 行的授权委托文档和一个包含 7 个工具的 AP 服务上进行了端到端测试。系统提取了 9 条规则,并将 3 个句子保留为 TODO。审阅后,网关会拒绝四种操作:导致滚动 24 小时总额超过 $50,000 的付款、对已付款发票的再次付款、写入 .ledger 文件,以及写入 /finance/ap/ 目录之外的位置。其中一个 TODO 是“准备付款的人不得批准该付款”。这是一条本层无法表达的真实规则,它会以注释形式出现在输出中,而不是悄无声息地消失。
为你自己的工作负载添加规则
以上每个选项都在调整别人选择的行为。要添加“在我们这里,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 只有一个方法:
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 会针对植入在一张 Agent 获准读取的工单中的提示词注入攻击,端到端运行整个流程。第四次调用会获准,第五次调用会转交给人工处理。
**在此拦截攻击的是出口允许列表,这是一条针对单次调用的规则。**注入的地址不在允许的域名范围内,因此最基础的防护规则会将其拦截,不会查询来源层。这是网关按正确顺序工作的结果,并非本文声称具有独特性的能力演示,因此示例会打印触发的层级,而 examples/README.md 会移除域名列表,使用相同会话运行,此时由来源层作出判断。
如果你的策略指定了一个域名,而攻击者指定了该域名内的地址,基础防护规则就无法判断,此时由 egress.recipients 限定收件邮箱。此项默认未启用,clayseal policy lint 也不会要求配置,因此允许某个域名的策略会允许该域名下的所有邮箱。
安全态势
除非环境明确标记为开发环境,否则防护机制都会默认拒绝。
CLAYSEAL_ENV=development # relaxes them, and says so once per process
未设置该环境,或将其设为 production,都意味着:拒绝未固定提交令牌签发密钥的操作、拒绝缺失重放存储的操作、拒绝未签名的升级审批,并拒绝来自未固定签名者的意图信封。过去采用的默认规则恰好相反,因此所有未读过本节的部署都会接受这四种情况。
请参阅 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 签名 Agent 卡片。
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 是索引。你最可能需要查看:
- 前十分钟,了解如何从演示开始构建自己的 Agent
- 如果你是负责部署此项目的编码 Agent,请参阅 Agent 说明
- llms.txt,机器可读的索引
- 证据,列出所有测量数值及其未超出的限制
- API 参考,按大多数集成实际使用的情况分级列出导出名称
- 开发者指南,介绍如何安装并接入
- 策略参考,说明策略文件可以包含哪些内容
- 威胁模型,说明它防御哪些威胁,以及不防御哪些威胁
- 隐私与数据处理,说明它存储哪些内容,以及哪些内容会离开进程
如需报告漏洞,请参阅 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。
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。