内容
# 代理防护栏
[](https://github.com/ss1738/agent-guardrail/actions/workflows/ci.yml)
[](LICENSE)
<p align="center">
<img src="docs/demo.svg" alt="一个被劫持的编码代理试图强制推送 main,删除 repo,泄露密钥并清空 CI;agent-guardrail 阻止了所有这些操作,而真正的修复程序则顺利通过" width="760">
</p>
**自主编码代理的运行时防护栏。它作为一个 MCP 服务器在代理和您的仓库之间的工具调用路径中运行,因此每个 `run_shell` / `write_file` / `git` 调用都会在执行前进行检查。它阻止破坏仓库的操作(强制推送 `main`,删除整个仓库,泄露密钥,清空 CI),并允许正常的构建和提交工作通过,而不信任代理的行为。 作为奖励,其 git 分支子策略由 z3 进行机器检查,这是几乎没有防护栏具有的属性:该策略可以自行检查是否存在漏洞。所有其他内容都是高精度启发式,而本文明确指出哪些是启发式。**
编码代理(Copilot Workspace,SWE-agent,OpenHands)现在可以自行打开 PR,运行 shell 脚本,并重写 git 历史记录。它们的安全性目前取决于模型的表现:提示防护栏和对齐。这是有概率的,取决于模型。 一个对齐良好的模型可能会拒绝提示注入;一个破解或较弱的模型则不会。 agent-guardrail 是确定性的层次,不依赖于模型:它在每个工具调用运行时进行检查,因此即使代理被破坏,也能阻止其行为。
## 防护栏之外:可验证的收据(代理控制平面)
防护栏可以阻止恶意操作。 **代理控制平面** 添加了另一个可靠方需要的部分:一个签名 **收据**,任何人都可以验证 *而无需信任您或您的服务器*。 将代理身份绑定到一个命名的、已提交的策略,控制其工具调用,并导出收据 — 代理 ID,策略承诺,每个受控操作及其判决结果,以及收据的 Ed25519 签名。 审计员、银行或保险公司仅凭 **公钥** 即可验证:
```bash
agent-guardrail-verify receipt.json
# VERIFIED: verified: 6 actions, policy acme-prod-agent-policy, untampered and sound
```
`verify_receipt` 运行四个独立的检查:(1)收据中提到的策略与验证者持有的策略相符(承诺匹配);(2)公共哈希链完好无损(无插入 / 删除 / 重排 / 修改);(3)Ed25519 签名有效;以及(4)— 最重要的 — 它 **重新运行已提交的策略来验证跟踪**,因此即使操作员重新链接并用自己的密钥重新签名,也能捕获伪造的 `ALLOW` 操作。
```bash
python3 demo_receipt.py # 一个受控会话 -> VERIFIED 独立 -> 操作员伪造 + 重新签名 -> CAUGHT
```
收据携带自己的密钥,因此它本身就证明了 *某些* 密钥签署了合规的跟踪。 将每个代理的公钥一次性注册到您控制的注册表中(`agent-guardrail-verify receipt.json --registry agents.json`),然后验证还证明收据来自 *该* 代理 — 攻击者签署合规跟踪但冒充注册身份将被拒绝。
**隐私 — 在不破坏证明的情况下编辑。** 链是基于每个操作的加盐承诺的,因此 `receipt.redact()` 可以删除原始命令和文件内容,同时签名和完整性保持有效;见证人可以披露任意 *子集* 操作来证明判决结果是合理的。 您可以向保险公司提供证明您的代理在策略范围内运行,而无需提供生产命令。 验证结果明确指出披露了多少内容,因此部分编辑的收据永远不会被误认为是完全合理的收据。
```bash
agent-guardrail-redact receipt.json --out redacted.json --witness witness.json
agent-guardrail-verify redacted.json # 完整性 + 真实性,内容未泄露
agent-guardrail-verify redacted.json --witness witness.json # + 完整性,来自见证人
```
**零知识收据(实验性,仅限 git 分支策略)。** 选择性披露仍然需要为每个操作做出选择:揭示它,否则失去其合理性证明。 在 zk 模式下(`ControlPlane(agent_id, policy, zk=True)`),git 分支操作携带一个零知识证明,证明它是该策略分类为记录的判决的其中一个,基于与链中相同的承诺。 您可以完全编辑操作,验证者仍然可以确认判决是正确的,并且仍然可以捕获伪造的“它被允许”(`demo_zk_receipt.py`)。 证明在一个可插拔群组上运行:默认是一个 2048 位的 MODP 群组;`zk="ec"` 选择 secp256k1,大约快 10 倍,小 8 倍(已测量)。 这是原型加密技术(带有 Fiat-Shamir 的 Sigma OR 证明,在随机预言模型中),在依赖它作为保证之前需要外部审查;设计、范围和路线图在 [docs/ZK_ROADMAP.md](docs/ZK_ROADMAP.md) 中。
通过 MCP 服务器,`session_receipt` 工具导出会话的收据。 **范围,诚实:** 收据证明记录的跟踪在已提交的策略下是合理受控的;它不证明模型的意图,其执行力度与规则集和防护栏的力度相同(与假设相同 - 参见下方)。 [docs/CONTROL_PLANE_TRUST_MODEL.md](docs/CONTROL_PLANE_TRUST_MODEL.md) 中明确说明了 `VERIFIED` 收据的保证内容和不保证内容 — 安全审查或保险公司应阅读的文档。
## 什么是证明的,与什么是启发式的
诚实地说明这一点,因为这是重点:
| 威胁 | 如何强制执行 | 保证 |
|---|---|---|
| 强制推送 / 硬重置 / 重新基准的保护分支 | 结构化 git 策略 | **由 z3 机器检查**(见下文) |
| 删除整个仓库 / 主目录 / 根目录 | 命令的正则表达式 | 启发式,高精度,可规避 |
| 密钥写入磁盘或通过网络泄露 | 正则表达式(密钥形状 + 接收器) | 启发式 |
| 清空 CI 工作流文件 | 路径 + 内容检查 | 启发式 |
| 构建 / 测试代理执行的所有其他操作 | 默认允许 | 不受监管(按设计) |
"启发式"意味着它会捕获直接形式并故意规避确定性的混淆者(`rm -r -f`, `$IFS`,base64,别名)。 它提高了事故或天真注入的成本;它不是沙盒。 对于硬隔离,请将其与 seccomp / gVisor 结合使用(参见“比较”)。
## 演示(有测量,可重复)
相同的破坏性工具调用序列,被劫持的代理发出,在真实的仓库上运行,有无防护栏(`demo_compare.py`):
| | `.git` 完整 | 磁盘上的密钥 | CI 配置完整 | 合法修复仍然适用 |
|---|:---:|:---:|:---:|:---:|
| **无防护栏** | 已销毁 | 已泄露 | 已清空 | 是 |
| **有防护栏** | 是 | 无 | 是 | 是 |
6 个破坏性操作被阻止,2 个合法操作被允许,在实际执行路径中。
## 机器检查的核心(可信度细节,不是主要防御)
这涵盖了最窄的范围,即受保护的分支历史记录,服务器端 `pre-receive` 钩子也对此进行保护,因此这不是价值所在。 它在这里因为它很不寻常:一个手动编写的允许列表无法告诉你它是否有漏洞,而这个策略可以。 使用 z3,它证明了 git 操作的符号类:
```
guard_allows(a) implies not mutates_protected_history(a)
```
安全性规格独立于防护栏编写,而不是从中复制,因此结果不是循环的。 它返回 `PROVED`,或者一个具体的反例。 删除一条规则,它会找到确切的漏洞:
```
完整策略 : PROVED (无允许操作重写保护历史)
删除 rebase 规则 : HOLE 操作 = rebase 分支 = main
```
此保证仅涵盖结构化的 git 工具调用路径。 它不验证 Python 实现,正则表达式规则或 shell 解析器。
## 比较
| 工具 | 层 | 捕获 | 为什么不同 |
|---|---|---|---|
| git 服务器端钩子(`pre-receive`) | git 服务器 | 推送后不良操作,一旦到达服务器 | agent-guardrail 在本地运行前控制操作,还涵盖文件 / shell,而不仅仅是推送 |
| seccomp / gVisor | 系统调用 / 内核 | 系统调用级隔离 | 强大的隔离,但没有“受保护的分支”或“这是密钥”的概念;互补,不替代 |
| OPA / 策略引擎 | 请求 / API | 结构化策略决策 | 一般策略,无策略的正式自我检查,也无开箱的编码代理操作模型 |
| CodeQL / Copilot Autofix | 静态分析 | 代码中的漏洞 | 分析静态代码;对运行时仓库中代理的操作无能为力 |
| git 钩子 + shell 允许列表 | 本地 | 一些相同的模式 | 无法证明策略无漏洞;这是 z3 增加的内容 |
简而言之:这不是沙盒也不是 linter。 它是编码代理的操作级防护栏,具有一个正式检查的核心,仅限于一个子策略,其中证明是可行的。 在沙盒前使用它,而不是代替它。
## 验证误伤(不是攻击检测)
一个阻塞合法工作的防护栏会在两个拉取请求后关闭,因此值得衡量的是它阻挡合法命令的频率。 它在 **2,836 个真实 `run:` 命令**(来自 12 个受信任的仓库)(`rust-lang/rust`,`tokio`,`denoland/deno`,`ripgrep`,`cargo`,`ruff`)上运行,只读,无执行(`realworld_test.py`):
| 版本 | 误伤 | 升级 | 允许 |
|---|:---:|:---:|:---:|
| v0(关闭任意元字符) | 2(均为合法 CI) | 1854(65%) | 35% |
| v0.1(默认允许,阻止威胁模型) | 0 | 0 | 100% |
然后在更大的样本中重新运行,以检查选择偏差:**来自 49 个最受欢迎的仓库的 3,790 个真实命令**,跨 9 种语言(`react`,`next.js`,`tensorflow`,`ollama`,`godot`,`deno`,`rust`等),按 GitHub 搜索排名收集,而不是手动选择(`scripts/expanded_realworld.py`)。 结果:**99.9% 允许,0 次误伤,2 次正确升级**(强制推送和重基,其分支是 CI 变量,因此防护栏无法判断它是否受保护并询问人类)。
更大的样本值得信赖:它捕获了小型样本遗漏的 **四个误伤**(`rm -f ../private.key` 和 `rm -rf ./bin/build_deps`,一个特定文件和一个子路径,`rm` 规则过度匹配为仓库销毁)。 已修复,所有四个都是回归测试。 这就是为什么这个数字值得信赖:它在未经选择的代码上进行了测试,它发现的故障被修复而不是隐藏。
诚实地阅读:采用默认允许设计,合法命令的低误伤率是 **目标**,而不是惊喜。 语料库的价值在于它揭示的相反故障(v0 被关闭并阻止三分之二的正常 CI;`rm` 规则过度匹配真实的子路径删除)以及它们得到修复。 该语料库 **不** 衡量攻击检测;它不包含对抗性命令。 攻击行为由演示和测试覆盖,而不是由这个数字。
## 假设和限制
- **防护栏必须与代理分开运行。** 如果代理可以编辑防护栏或读取 HMAC 密钥,它可以绕过或伪造。 审计链在假设密钥对代理不可访问的情况下是防篡改的(包装进程或单独服务,而不是代理控制的进程导入)。 参见 [THREAT_MODEL.md](THREAT_MODEL.md)。
- **Shell 覆盖是尽力而为的。** 确定的攻击者会混淆正则表达式。 将 Shell 阻止视为深度防御,而不是边界。
- **一个对齐良好的代理通常会自行拒绝注入。** 在 `demo_hijack.py` 中,一个真实的 Claude 代理读取了植入的笔记并拒绝了它。 防护栏的价值在于成为当对齐失败时的确定性备用。
## 与任何 MCP 代理一起使用
防护栏作为一个 [MCP](https://modelcontextprotocol.io) 服务器运行,这是 Claude Desktop、Cursor、Copilot 和 Windsurf 都使用的工具调用表面。 将 MCP 客户端指向它,代理发出的每个 `run_shell` / `write_file` / `git` 调用都会在协议路径中受到防护。 不信任代理。
这仅在 MCP 服务器是代理的 **唯一** shell、git 和文件路由时才成立。 代理还具有不受保护的原始 shell,或可以编辑服务器文件,则可以绕过防护栏。 在单独的进程中运行它,并给代理其他执行路径。 参见 [THREAT_MODEL.md](THREAT_MODEL.md)。
```jsonc
// 在您的 MCP 客户端配置中(Claude Desktop,Cursor,...)
"agent-guardrail": {
"command": "python3",
"args": ["/path/to/agent-guardrail/integrations/mcp_server.py"],
"env": { "GUARDRAIL_WORKSPACE": "/path/to/your/repo" }
}
```
`integrations/demo_mcp.py` 演示了这一点:它启动服务器并作为真正的 MCP 客户端通过 stdio(那些应用程序使用的相同传输)连接,然后发出合法和破坏性的工具调用的混合。 破坏性的操作在协议内被阻止:
```
Tools exposed to the agent via MCP: run_shell, write_file, git, audit_log
▶ write_file('calc.py') wrote calc.py (修复)
▶ git('add calc.py') ok
⛔ git('push origin main --force') BLOCK 强制推送到受保护的分支 'main'
⛔ run_shell('rm -rf .git') BLOCK 递归删除仓库
⛔ write_file('config.py' w/ token) BLOCK 将密钥写入磁盘
⛔ run_shell('curl ... -d ghp_...') BLOCK 通过网络泄露密钥
⛔ write_file('.github/workflows/...') BLOCK 清空 CI 配置
▶ run_shell('cargo build --release') allowed
blocked: 5 allowed: 4 .git intact, no secret, CI intact, fix applied, audit chain verifies
```
## 运行它
```bash
pip install -e . # 安装软件包 + `agent-guardrail-verify` 命令
# (或者,只需运行下面的脚本而无需 CLI:pip install z3-solver cryptography "mcp")
python3 tests/test_guardrail.py # 11 测试(防护栏)
python3 tests/test_control_plane.py # 18 测试(可验证的收据 + 0/500 伪造被捕获)
python3 demo_compare.py # 有无防护栏对比,基于真实的仓库
python3 demo_receipt.py # 一个可验证的收据:发出,验证,以及伪造被捕获
python3 integrations/demo_mcp.py # 防护栏在真正的 MCP 工具调用路径中
python3 realworld_test.py # 2,836 命令的摩擦检查(需要 gh)
ANTHROPIC_API_KEY=... python3 demo_hijack.py # 一个真实的 Claude 代理遇到一个植入的注入:它拒绝,并且防护栏站在它后面
```
拦截本身(实际上被阻止的攻击)由 `demo_compare.py` 和 `integrations/demo_mcp.py` 展示。 `demo_hijack.py` 展示了互补案例:一个行为良好的真实代理,防护栏增加的摩擦为零,并且作为备用。
## 状态
早期,单一作者,诚实地说明范围。 欢迎反馈和策略中的漏洞,尤其是漏洞。 参见 [THREAT_MODEL.md](THREAT_MODEL.md) 和 [SECURITY.md](SECURITY.md)。
MIT License.