Agent 工具执行安全框架
控制 Agent 的"手"------工具执行层安全的通用架构设计。与 Agent 引擎无关,可被任何 Agent 项目参考复用。
一、定位
行业已有成熟的大模型统一网关方案,控制 Agent 的"脑"------模型访问、Token 限流、Prompt 安全、输出审核、成本管控。
但 Agent 不只是聊天。Agent 的本质是通过工具与真实系统交互。网关管不了 Agent 读了哪个文件、跑了什么命令、访问了哪台内部服务。
本框架补上这块:控制 Agent 的"手"------工具执行层。
arduino
┌─────────────────────┐ ┌─────────────────────┐
│ LLM 统一网关 (已有) │ │ 工具执行安全层 (本框架) │
│ 控制"脑" │ │ 控制"手" │
│ │ │ │
│ • 模型访问控制 │ │ • 数据安全 │
│ • Token / 限流 │ ←───→ │ • 操作安全 │
│ • Prompt 注入防护 │ │ • 系统安全 │
│ • 输出内容审核 │ │ • 网络安全 │
│ • 成本管控 │ │ • 权限安全 │
│ │ │ • 合规审计 │
└─────────────────────┘ └─────────────────────┘
两层合在一起 = 企业级 Agent 安全闭环
网关管住 用户 ↔ LLM 的对话通道(输入过滤 + 输出审核)。 本框架管住 LLM ↔ 真实世界 的操作通道(工具输入拦截 + 结果输出扫描)。
威胁模型
| 威胁类别 | 攻击场景 |
|---|---|
| 数据泄露 | Agent 通过 read_file 读到 API Key、私钥、PII,数据进入 LLM 上下文后不可控 |
| 破坏性操作 | Agent 执行 rm -rf /、覆写系统配置、注入恶意依赖 |
| 资源耗尽 | fork bomb、死循环、大量内存分配,导致宿主机不可用 |
| 网络逃逸 | Agent 通过 exec(curl ...) 或 web_fetch 访问内部服务或向外泄露数据 |
| 权限提升 | Agent 通过 spawn 创建不受控子 Agent,通过 cron 持久化任务 |
| 供应链攻击 | 恶意 Skills/插件被安装后获得工具执行权限 |
二、核心设计原则
原则 1:唯一咽喉(Single Chokepoint)
无论什么 Agent 引擎,所有工具调用最终都经过一个出口。拦住这个出口 = 控制 Agent 的全部真实操作能力。
scss
agent_loop()
→ tool_registry.execute(name, params) ← 唯一咽喉
→ tool.validate(params)
→ tool.run(**params)
主 Agent、子 Agent(spawn)、MCP 工具全部经过此入口。在类/trait 级别替换一次,所有实例自动生效。
原则 2:三阶段流水线(Before / Around / After)
makefile
Before: 策略检查 + 插件钩子(是否允许执行)
Around: 选择执行环境(宿主机 / 容器)+ 实际执行
After: DLP 扫描 + 审计记录
三个阶段的输入输出严格定义,每个阶段可独立启用/禁用。
原则 3:零侵入(Runtime Weaving)
不 fork、不改 Agent 引擎源码,运行时注入。
- Python: monkey-patch 类方法
- Node.js: Proxy/Reflect 或原型链替换
- Go: 接口适配器包装
- Rust: trait 实现替换
为什么这很重要:改源码 = fork,fork = 维护负担,维护负担 = 跟不上上游演进。零侵入意味着 Agent 引擎可以源源不断地升级,安全层只管自己的事。
兼容性风险与缓解:Agent 引擎升级后,咽喉点的方法签名可能变化(参数增减、方法重命名),导致 monkey-patch 静默失效。缓解措施:
- 启动时校验:安全层注入前检查目标方法是否存在、签名是否符合预期(参数名/参数数量)。校验失败时拒绝启动并告警,而非静默降级
- 版本声明 :安全层配置中声明兼容的 Agent 引擎版本范围(如
engine_compat: "nanobot>=0.9,<2.0") - 降级策略:校验失败时可选行为------拒绝启动(安全优先)或跳过安全层并告警(可用性优先)
原则 4:双层沙箱(Transparent + Isolated)
执行环境和策略模式是两个正交维度,任意组合:
scss
┌─────────────────────────────────────┐
│ 策略模式 (Policy Mode) │
│ monitor │ enforce │ disable │
┌─────────────┼──────────┼─────────┼─────────────────┤
│ transparent │ 记录不拦 │ 违规拦截 │ 跳过安全层 │
│ (宿主机直执) │ │ │ │
├─────────────┼──────────┼─────────┼─────────────────┤
│ restricted │ 记录不拦 │ 违规拦截 │ 跳过安全层 │
│ (子进程+限制)│+资源隔离 │+资源隔离 │+资源隔离 │
├─────────────┼──────────┼─────────┼─────────────────┤
│ isolated │ 记录不拦 │ 违规拦截 │ 跳过安全层 │
│ (容器) │+物理隔离 │+物理隔离 │+物理隔离 │
└─────────────┴──────────┴─────────┴─────────────────┘
执行环境轴 策略模式轴
执行环境决定在哪里跑 (宿主机 / 受限子进程 / 容器),策略模式决定怎么管 (只审计 / 阻断 / 不管)。两者独立配置,策略/钩子/DLP/审计在所有执行环境上同样生效。
原则 5:策略与钩子分离
scss
内置策略 (Policy):配置驱动的静态规则,受 mode 控制
→ enforce 模式下阻断,monitor 模式下仅记录
插件钩子 (Hooks):用户自定义的动态逻辑,始终权威
→ 无论 mode 为何值,钩子的 deny 一律阻止执行
为什么要分离 :用户安装了自定义安全插件(如交互式审批),当用户明确点击"拒绝"时,这个决策不应该被系统的 monitor 模式覆盖。用户的显式决策永远优先于系统默认行为。
原则 6:人机协作(Human-in-the-Loop)
Agent 在执行关键操作前,可以暂停等待人类确认。安全层提供异步审批通道,让人类在关键时刻介入决策,而 Agent 引擎本身不知道发生了什么------它只看到"工具执行慢了一点"。
三、三阶段流水线(核心架构)
yaml
┌──────────────────────────────────────────────────┐
│ tool_registry.execute(name, params) │
│ │
│ ┌──────────────┐ │
│ │ Phase 1a │ 内置策略检查(受 mode 控制) │
│ │ Policy Gate │ • 工具白/黑名单 │
│ │ │ • 路径 ACL │
│ │ │ • 命令黑名单 │
│ │ │ • 域名白名单 │
│ │ │ │
│ │ monitor: 记录但不阻断 │
│ │ enforce: 拒绝 → 返回 Error │
│ └──────────────┘ │
│ ┌──────────────┐ │
│ │ Phase 1b │ 插件钩子(始终权威) │
│ │ Plugin Hooks │ • 同步钩子: on_before(tool, params)│
│ │ │ • 异步钩子: on_before(tool, params, ctx)│
│ │ │ • 支持交互式审批 │
│ │ │ │
│ │ deny → 一律阻止,不受 mode 影响 │
│ │ allow → 继续执行 │
│ └──────────────┘ │
│ ┌──────────────┐ │
│ │ Phase 2 │ 执行环境 │
│ │ Around │ • transparent: 宿主机直接执行 │
│ │ │ • restricted: 子进程+资源限制 │
│ │ │ • isolated: 容器内执行 │
│ └──────────────┘ │
│ ┌──────────────┐ │
│ │ Phase 3 │ 审计 + DLP │
│ │ After │ • 结果敏感数据扫描 │
│ │ │ • CRITICAL → 阻断 │
│ │ │ • HIGH → 脱敏 │
│ │ │ • 结构化日志 → SIEM │
│ └──────────────┘ │
│ │ │
│ ▼ result 返回 LLM 上下文 │
└──────────────────────────────────────────────────┘
三阶段数据流
| 阶段 | 输入 | 输出 | 可扩展点 |
|---|---|---|---|
| Before (1a) | tool_name, params, policy | allowed / denied / monitored | 策略文件热加载 |
| Before (1b) | tool_name, params, HookContext | allowed / denied + reason + message | 自定义插件 |
| Around (2) | tool_name, params, sandbox_mode | result 或 exception | 执行环境选择 |
| After (3) | result, DLP patterns | processed_result, audit_record | 自定义 DLP 模式、SIEM 对接 |
四、策略引擎规范
策略文件独立于代码,支持热加载:
json
{
"mode": "enforce",
"dlp": {
"enabled": true,
"on_critical": "block",
"on_high": "redact"
},
"tools": {
"read_file": {
"action": "allow",
"denied_paths": ["**/.env", "**/.env.*", "**/credentials*", "**/*.pem", "~/.ssh/**"]
},
"write_file": {
"action": "allow",
"denied_paths": ["**/.git/**", "/etc/**", "/usr/**"]
},
"exec": {
"action": "allow",
"sandbox": "restricted",
"denied_commands": [
"^sudo\\b",
"\\brm\\s+-rf\\s+/",
"\\bcurl\\b.*\\|\\s*(bash|sh)",
"\\bchmod\\s+777\\b",
"\\bshutdown\\b",
"\\breboot\\b"
],
"max_timeout": 60
},
"web_fetch": {
"action": "allow",
"allowed_domains": ["*.github.com", "docs.python.org"],
"denied_domains": ["*"]
},
"spawn": {
"action": "allow",
"max_concurrent": 3,
"inherit_policy": true
},
"cron": {
"action": "deny"
}
}
}
策略模式
| 模式 | 行为 |
|---|---|
enforce |
违规时阻止执行,DLP 命中时阻断/脱敏 |
monitor |
仅记录日志,不阻止任何调用(用于策略调优阶段) |
disable |
跳过安全层(不推荐用于生产) |
策略层级与优先级
全局 mode 和工具级 action 的关系:
| 全局 mode | 工具 action | 最终行为 |
|---|---|---|
enforce |
allow + 规则通过 |
允许 |
enforce |
allow + 规则不通过 |
拒绝 |
enforce |
deny |
拒绝 |
enforce |
monitor |
允许 + 记录 |
monitor |
allow + 规则不通过 |
允许 + 记录违规 |
monitor |
deny |
允许 + 记录违规 |
disable |
任意 | 允许(跳过安全层) |
简言之:
disable→ 一切放行monitor→ 一切放行,但违规会被记录(用于策略调优阶段)enforce→ 按工具级规则执行
例外 :插件钩子的决策不受全局 mode 影响 。即使 mode: monitor,插件返回 deny 仍然阻止执行(见原则 5)。
策略规则字段
| 字段 | 适用工具 | 说明 |
|---|---|---|
action |
所有 | allow / deny / monitor |
denied_paths |
read_file, write_file, edit_file | glob 模式黑名单 |
allowed_paths |
read_file, write_file, edit_file | glob 模式白名单 |
denied_commands |
exec | 正则表达式黑名单 |
allowed_domains |
web_fetch | 域名白名单 |
denied_domains |
web_fetch | 域名黑名单 |
sandbox |
exec | transparent / restricted |
max_timeout |
exec | 最大执行时间(秒) |
inherit_policy |
spawn | 子 Agent 是否继承父策略 |
五、插件系统规范
安全层通过约定的插件目录(如 ~/.your-agent/security-plugins/)加载用户自定义逻辑。
插件发现与加载
- 目录扫描:
*.py(Python)或其他语言的约定扩展名 - 按文件名字母序加载,钩子按注册顺序执行
- 第一个返回拒绝的 Before Hook 即终止执行链
Before Hook 接口
同步钩子(简单场景):
python
def on_before(tool_name: str, params: dict) -> dict | None:
"""返回 None 表示放行,返回 dict 表示拦截。"""
if tool_name == "exec" and "rm" in params.get("command", ""):
return {
"allowed": False,
"reason": "Dangerous command detected",
"message": "This command was blocked for safety reasons.",
}
return None
异步钩子(需要用户交互):
python
async def on_before(tool_name: str, params: dict, ctx=None) -> dict | None:
"""异步钩子,通过 ctx.request_approval() 请求用户审批。"""
if ctx is None:
return None # 无交互通道时默认放行
decision = await ctx.request_approval(tool_name, params, summary="...")
if decision.get("action") == "deny":
return {
"allowed": False,
"reason": "User denied",
"message": "User declined this operation. Ask what to do instead.",
}
return None
Hook 返回值规范
| 字段 | 类型 | 说明 |
|---|---|---|
allowed |
bool |
False 表示拦截 |
reason |
str |
内部日志原因(出现在审计日志中) |
message |
str(可选) |
Agent 侧可见的消息。插件通过此字段控制 AI 收到的拒绝理由。如未提供,使用默认文案 |
message 字段的设计意图:区分"用户主动拒绝"和"安全策略阻止"。当用户在 UI 上点击"取消"时,Agent 收到的应该是"用户不想执行这个操作",而不是"被安全策略阻止"------这影响 Agent 的后续行为。
After Hook 接口
python
def on_after(record) -> None:
"""审计后置钩子,可对接外部 SIEM 系统。"""
if record.dlp_findings:
send_to_siem(record)
自定义 DLP 模式
python
DLP_PATTERNS = {
"INTERNAL_IP": [r"\b10\.\d{1,3}\.\d{1,3}\.\d{1,3}\b"],
"COMPANY_SECRET": [r"(?i)PROJECT[-_]?CODENAME[-_]?\w+"],
}
关键设计决策:插件始终权威
内置策略受 mode 控制------monitor 模式下仅记录不阻断。但插件钩子的决策始终权威 ,不受 mode 影响。
这确保了:用户安装的自定义安全插件(如交互式审批)中,用户明确点击"拒绝"时,即使系统处于 monitor 模式,工具也会被阻止。
六、交互式审批通道
问题背景
许多场景下,Agent 在执行工具前需要人类确认------类似 Cursor 的 Run/Cancel 交互。安全层需要提供一种机制,让插件在工具执行前暂停并等待人类决策。
通道机制
scss
插件 on_before()
→ ctx.request_approval(tool, params, summary)
│
│ 异步 Future 创建,挂起等待
│
┌───┴───┐
│ 双向 │ WebSocket / IPC / 或任何双向通信
│ 通信 │
└───┬───┘
↓
前端 UI 显示审批卡片
[Run] [Allowlist] [Cancel]
│
┌───┴───┐
│ 双向 │
│ 通信 │
└───┬───┘
↓
插件收到 decision
{action: "allow_once" | "allowlist" | "deny"}
审批协议
请求(安全层 → 前端):
json
{
"type": "tool_approval",
"id": "uuid-request-id",
"tool": "exec",
"params": {"command": "npm install express"},
"summary": "npm install express"
}
响应(前端 → 安全层):
json
{
"type": "tool_approval_response",
"id": "uuid-request-id",
"action": "allow_once"
}
无超时设计
审批请求无限期挂起,直到:
- 用户明确响应(Run / Allowlist / Cancel)
- 连接断开(应用关闭、进程被杀等)→ 自动视为 deny
为什么不设超时:工具执行是关键操作。如果用户离开电脑去喝咖啡,回来后工具不应该因为超时而自动执行或自动拒绝。挂起是最安全的默认行为。
非交互场景降级
Cron 任务、API 调用等无 UI 的场景下,审批通道无法弹出 UI。降级行为可配置:
| 降级模式 | 行为 | 适用场景 |
|---|---|---|
allow |
自动放行,不阻塞 | 可信自动化任务 |
deny |
自动拒绝 | 高安全要求环境 |
policy_only |
跳过审批,仅走策略引擎 | 推荐默认值 |
json
{
"approval": {
"non_interactive_fallback": "policy_only"
}
}
白名单机制
支持两维度白名单,减少重复审批:
json
{
"tools": ["list_dir", "read_file", "exec:git *", "exec:npm run *"],
"paths": ["/Users/me/projects/**", "https://api.example.com/*"]
}
- tools 维度 :
tool_name或tool_name:command_pattern(glob 通配符) - paths 维度 :glob 模式匹配
path/url参数 - 匹配规则 :两个维度都匹配时才自动放行。无路径参数的工具(如
exec)自动跳过 paths 维度检查
七、执行环境抽象
透明沙箱(Transparent Sandbox)
工具在用户环境执行,通过策略/钩子/审批控制行为。
| 子模式 | 执行方式 | 适用场景 |
|---|---|---|
transparent |
宿主机直接执行 | 开发环境、可信场景 |
restricted |
宿主机子进程执行 + setrlimit 资源限制 |
需要资源防护但不需要完全隔离 |
restricted 模式的资源限制:
python
resource.setrlimit(resource.RLIMIT_AS, (mem_limit,) * 2) # 内存上限
resource.setrlimit(resource.RLIMIT_CPU, (cpu_limit,) * 2) # CPU 时间上限
resource.setrlimit(resource.RLIMIT_NPROC, (nproc_limit,) * 2) # 进程数上限
resource.setrlimit(resource.RLIMIT_FSIZE, (fsize_limit,) * 2) # 单文件大小上限
隔离沙箱(Isolated Sandbox)
整个 Agent 进程(含安全层)运行在容器内,与宿主机物理隔离。
arduino
宿主机 容器
┌─────────────────────┐ ┌─────────────────────┐
│ Shell / 前端 UI │ │ Gateway + Agent │
│ │ HTTP │ + 安全层(同一份代码)│
│ AdapterManager ├─────→│ │
│ (spawn 或 docker run)│ WS │ 所有工具在此执行 │
└─────────────────────┘ └─────────────────────┘
核心设计 :安全层代码在两种沙箱中零改动复用 。透明沙箱下直接执行,隔离沙箱下 docker run <your-agent-image>,同样的代码、同样的策略、同样的插件钩子。
Volume 挂载策略
| 数据 | 挂载方式 | 理由 |
|---|---|---|
Agent 配置 (config.json) |
只读 | 容器不能改配置,改配置只能通过宿主机 |
会话数据 (sessions/) |
读写 | 切换模式后历史无缝延续 |
| 安全策略/插件 | 只读 | 容器内不能篡改安全规则 |
| 用户工作区 | 用户决定 | 挂不挂、只读还是读写,是用户的安全/便利权衡 |
网络隔离
| 模式 | 网络 | 适用场景 |
|---|---|---|
--network bridge |
容器可访问互联网 | Agent 需要调用 LLM API、web_fetch |
--network none + 宿主机 LLM 代理 |
容器完全无网络 | 最高安全级别,API Key 不进容器 |
--network none 模式下,宿主机运行轻量 HTTP 代理,容器内的 LLM 请求通过代理转发,代理注入真实 API Key。这是实现真正网络隔离的唯一路径------否则容器要么无法调用 LLM,要么能访问任意网络地址。
切换机制
客户端在设置中切换执行环境(宿主机 / 容器),重启生效。这是运行时配置,不是安全策略------安全策略在两种环境下同样适用。
八、安全维度全景
六个安全维度,每个维度由框架的具体组件覆盖:
8.1 数据安全
威胁:Agent 读到敏感数据,数据进入 LLM 上下文后不可控。
控制点:
| 阶段 | 组件 | 手段 |
|---|---|---|
| 执行前 | 策略引擎 (Before) | 路径 ACL --- denied_paths 拒绝 .env / *.pem / ~/.ssh/** |
| 执行后 | DLP 扫描 (After) | 结果敏感数据分类 → CRITICAL 阻断 / HIGH 脱敏 |
内置 DLP 模式:
| 类别 | 检测内容 | 级别 |
|---|---|---|
CREDENTIAL |
API Key、密码、私钥、AWS Key、GitHub Token | CRITICAL |
PII |
身份证号、手机号 | HIGH |
FINANCIAL |
银行卡号 | HIGH |
插件可通过 DLP_PATTERNS 扩展自定义模式。
8.2 操作安全
威胁:Agent 执行破坏性操作。
控制点:
| 组件 | 手段 |
|---|---|
| 策略引擎 | exec.denied_commands 正则黑名单(sudo、rm -rf /、curl | bash 等) |
| 策略引擎 | write_file.denied_paths 保护系统目录和构建文件 |
| 审批通道 | 关键操作前人类确认 |
8.3 系统安全
威胁:Agent 耗尽系统资源(fork bomb、死循环)。
控制点:
| 组件 | 手段 |
|---|---|
| 执行环境 (restricted) | 子进程隔离 + setrlimit 资源限制 |
| 执行环境 (isolated) | Docker --memory 512m --cpus 1.0 --read-only |
| OS 级加固 | seccomp-bpf(Linux)、sandbox-exec(macOS) |
8.4 网络安全
威胁:Agent 访问不该访问的内部服务或向外泄露数据。
控制点:
| 组件 | 手段 |
|---|---|
| 策略引擎 | web_fetch.allowed_domains / denied_domains |
| 策略引擎 | exec.denied_commands 拦截 curl/wget/ssh/nc |
| 执行环境 (isolated) | Docker --network none + 宿主机 LLM 代理 |
8.5 权限安全
威胁:Agent 通过 spawn 创建不受控子 Agent,通过 cron 持久化任务。
控制点:
| 组件 | 手段 |
|---|---|
| 策略引擎 | spawn.max_concurrent、spawn.inherit_policy |
| 策略引擎 | cron.action: deny(默认禁止,需审批放行) |
| 安全层 | 类级别替换确保子 Agent 自动继承安全层,无逃逸路径 |
8.6 合规审计
威胁:出了安全事件无法追溯。
控制点:
| 组件 | 手段 |
|---|---|
| 审计系统 | 每次工具调用产生结构化审计记录 |
| After Hook | 可对接企业 SIEM / ELK |
审计记录格式:
json
{
"ts": "2026-03-04T14:23:15.123Z",
"session_id": "sess-abc-123",
"agent_id": "main",
"sandbox_mode": "transparent",
"policy_mode": "enforce",
"tool": "read_file",
"params": {"path": "/workspace/config/db.yml"},
"decision": "allowed",
"decided_by": "policy",
"duration_ms": 12,
"dlp_findings": [{"category": "CREDENTIAL", "level": "CRITICAL"}],
"dlp_action": "redacted",
"result_size_bytes": 1523
}
关键字段说明:
| 字段 | 说明 |
|---|---|
session_id |
会话标识,关联同一对话的所有工具调用 |
agent_id |
区分主 Agent 和子 Agent(spawn 产生的) |
sandbox_mode |
执行时的沙箱环境 |
policy_mode |
执行时的策略模式 |
decided_by |
谁做的决策:policy / hook:插件名 / approval |
九、集成指南:如何对接你的 Agent 引擎
通用方法论
在任意 Agent 项目中应用本框架,只需三步:
- 找到咽喉点 :Agent 引擎中所有工具调用的唯一出口(通常是某个
execute()/run()/invoke()方法) - 运行时替换:用安全层包装原始方法,注入三阶段流水线
- 配置策略:提供外部策略文件,不硬编码安全规则
按语言的注入思路
不同语言有不同的运行时织入手法,但核心模式一致------保存原始方法 → 包装三阶段流水线 → 替换原始方法:
Python Agent(如 nanobot)
思路 :monkey-patch 类方法。找到 ToolRegistry 的 execute() 类方法,保存原始引用,替换为包含三阶段流水线的 async wrapper。因为是类级别替换,所有实例(含 spawn 产生的子 Agent)自动继承。
ini
original = ToolRegistry.execute
ToolRegistry.execute = wrapped_execute(original)
关键点:必须替换类方法而非实例方法,否则子 Agent 不会继承安全层。
Node.js Agent(如 OpenClaw)
思路 :原型链替换。通过 ToolRegistry.prototype.execute 替换原型方法,所有实例的调用自动经过安全层。也可以用 Proxy / Reflect 实现更细粒度的拦截。
ini
original = ToolRegistry.prototype.execute
ToolRegistry.prototype.execute = wrappedExecute(original)
关键点:Node.js 的事件循环天然支持异步审批等待,无需额外线程模型。
Go Agent(如 PicoClaw)
思路 :接口适配器(Decorator 模式)。Go 没有 monkey-patch,但接口组合天然支持包装。定义一个 SecuredExecutor 结构体,内嵌原始 ToolExecutor 接口,在 Execute() 方法中注入三阶段逻辑。
scss
SecuredExecutor{ inner: originalExecutor, policy: ... }
→ Execute() 中先 Before,再 inner.Execute(),再 After
关键点:Go 的接口隐式实现使得替换对调用方完全透明。
Rust Agent(如 ZeroClaw)
思路 :泛型 trait 包装。定义 SecuredTool<T: Tool>,对任意实现了 Tool trait 的类型 T 包装一层安全逻辑。编译期完成类型检查,运行时零开销。
r
SecuredTool<T: Tool> { inner: T, policy: ... }
→ impl Tool for SecuredTool<T>
→ execute() 中注入 Before / Around / After
关键点:Rust 的所有权系统确保安全层包装后,外部代码无法绕过包装直接访问内部 Tool。ZeroClaw 本身已内置了 Docker sandbox runtime(runtime.kind = "docker"),说明行业对 Agent 执行环境隔离的需求正在快速增长。
MCP 工具的特殊处理
MCP(Model Context Protocol)正在成为 Agent 工具集成的事实标准。MCP 工具与内置工具的关键区别:
| 差异 | 内置工具 | MCP 工具 |
|---|---|---|
| 工具名已知 | 编译时/启动时固定 | 运行时动态发现 |
| 参数结构 | 已知 schema | 由 MCP Server 定义,安全层未必了解 |
| 执行位置 | 本地进程内 | 可能在远程 MCP Server |
策略匹配 :对于动态工具名,策略引擎应支持通配符匹配(mcp:*、mcp:filesystem:*)和默认规则(未匹配的 MCP 工具走 default_mcp_action)。
DLP 扫描:MCP 工具返回值结构不固定,DLP 应对序列化后的完整字符串做正则扫描,而非依赖特定字段。
网络访问:远程 MCP Server 本身是一个网络出口。策略引擎应能控制允许连接哪些 MCP Server(通过 URL 白名单),防止 Agent 被引导连接恶意 MCP 端点。
json
{
"tools": {
"mcp:*": {
"action": "allow",
"allowed_servers": ["stdio://*", "http://localhost:*"],
"denied_servers": ["http://*", "https://*"]
}
}
}
如果 Agent 引擎的 MCP 工具调用同样经过咽喉点(大多数引擎如此),则安全层自动覆盖 MCP 工具,无需额外适配。
关键注意事项
| 要点 | 说明 |
|---|---|
| 子 Agent 继承 | 确保安全层在类/trait 级别替换,而非实例级别,这样 spawn 的子 Agent 自动继承 |
| 异步兼容 | Before Hook 可能需要等待用户审批(几秒到几分钟),确保替换后的方法支持异步 |
| 错误处理 | 安全层自身的异常不应导致 Agent 崩溃,应 catch 并降级为允许 |
十、参考实现概述
本框架已在一个基于 Python Agent 引擎的桌面 AI 助手项目中完整落地。以下是实现状态:
模块划分
实现分为四个核心模块,总计约 600 行 Python 代码:
| 模块 | 职责 |
|---|---|
| SecurityLayer | 核心协调器:monkey-patch 注入、策略加载、插件管理、三阶段流水线调度 |
| PolicyEngine | 策略引擎:JSON 策略解析、规则匹配、DLP 扫描、审计记录 |
| ApprovalChannel | 审批通道:异步 Future 管理、WebSocket 消息收发、连接断开处理 |
| BuiltinPlugins | 内置插件:交互式审批插件的自动安装与生命周期管理 |
已验证的能力
- 三阶段流水线(Policy Gate + Hooks + DLP + Audit)--- 日常使用中稳定运行
- 策略引擎(JSON 配置、热加载、enforce/monitor/disable 三模式)
- 安全插件系统(同步 + 异步钩子、自定义 DLP 模式)
- 交互式审批通道(WebSocket 双向通信 + 无超时 + 连接断开自动 deny)
- 白名单机制(tools + paths 两维度 glob 匹配)
- 内置插件自动安装(首次启动安装,用户删改后不覆盖)
- 策略与钩子分离(内置策略受 mode 控制,插件钩子始终权威)
- Hook
message字段(插件控制 Agent 侧可见消息,区分用户拒绝与策略阻止)
尚未实现
- 隔离沙箱(Docker 容器执行环境)--- 架构已设计,待开发
- restricted 子进程模式(setrlimit 资源限制)
- 宿主机 LLM 代理(
--network none下的 API Key 隔离)
实现体会
约 600 行代码覆盖了透明沙箱的全部能力,核心原因是架构约束做得好:唯一咽喉意味着只需替换一个方法;三阶段流水线意味着每个阶段职责单一;插件系统通过目录扫描 + 约定接口实现,无需复杂的注册机制。
十一、落地节奏
| 阶段 | 做什么 | 前置条件 |
|---|---|---|
| P1 | 透明沙箱上线(monitor 模式),全量记录工具调用,建立行为基线 | 咽喉点识别完成 |
| P2 | 策略引擎调优(根据 P1 审计数据),安全插件开发,交互式审批上线 | P1 数据积累 |
| P3 | 切 enforce,高危工具启用 restricted 模式 |
P2 策略验证通过 |
| P4 | 隔离沙箱(Docker)上线,宿主机 LLM 代理 | 容器化基础设施就绪 |
| P5 | 对接 IAM/RBAC,策略中心化管理,多租户 | 企业级部署需求 |
十二、横向对比:现有 Agent 安全方案
| 方案 | 类型 | 工具执行拦截 | 策略引擎 | 人机审批 | DLP | 零侵入 | 容器隔离 |
|---|---|---|---|---|---|---|---|
| Cursor | IDE 内置 | 有(Run/Cancel) | 无(硬编码) | 有 | 无 | N/A | 无 |
| Claude Code | CLI 内置 | 有(allowlist) | 有(.claude 配置) | 有 | 无 | N/A | 无 |
| ZeroClaw sandbox | 运行时内置 | 有(allowlist + workspace scope) | 有(config.toml) | 无 | 无 | N/A | 有(Docker) |
| PicoClaw sandbox | 运行时内置 | 有(restrict_to_workspace) | 有(命令黑名单) | 无 | 无 | N/A | 有(Docker) |
| 本框架 | 外挂安全层 | 有(三阶段流水线) | 有(JSON 热加载) | 有(异步审批通道) | 有 | 有 | 有(设计中) |
关键差异:
- 内置方案(Cursor、Claude Code、ZeroClaw、PicoClaw)的安全能力与 Agent 引擎耦合,无法跨引擎复用,也无法独立于引擎升级迭代
- 本框架作为外挂安全层,与 Agent 引擎完全解耦。同一套策略和插件可以应用于 Python、Node.js、Go、Rust 的不同 Agent
- 现有方案普遍缺少 DLP(数据防泄漏)------工具执行结果中的敏感数据(API Key、PII)直接进入 LLM 上下文,无拦截
- 现有方案的策略大多硬编码或仅支持简单配置,不支持插件式扩展 和热加载
十三、架构优势
安全层与 Agent 引擎完全解耦
markdown
┌─────────────────────────────────────────┐
│ 安全层(外挂模块) │
│ 策略引擎 / 插件 / 审批 / DLP / 审计 │
│ 独立部署、独立升级、独立配置 │
└──────────────────┬──────────────────────┘
│ 运行时织入(零侵入)
▼
┌─────────────────────────────────────────┐
│ Agent 引擎(任意) │
│ nanobot / OpenClaw / PicoClaw / ZeroClaw│
│ 独立演进、自由升级、社区同步 │
└─────────────────────────────────────────┘
这意味着:
- Agent 引擎可以持续升级:上游发布新版本直接升级,安全层不受影响
- 安全层独立迭代:策略更新、新增 DLP 模式、接入 SIEM------都不需要动 Agent 引擎
- 不 fork、不魔改:彻底避免 fork 开源项目后跟不上上游的困境
- 可插拔:想关掉安全层?移除注入代码重启即可,Agent 恢复原样
- 跨引擎复用:同一套安全策略和插件可应用于不同的 Agent 引擎
v1.0 | 2026-03-04 | Agent 工具执行安全框架 --- 从提案到架构设计 v0.2 | 2025-03-08 | 基于 nanobot (Python) 的企业级安全提案