权限控制
用声明式规则精确控制 Agent 的文件系统访问范围
从"全权委托"到"最小权限"的安全实践为什么需要它
你敢让 Agent 直接操作整个文件系统吗?
无权限控制的危险
- Agent 可以随意读取 .env、密钥等敏感文件
- 一次幻觉就可能覆盖重要配置或源代码
- 子 Agent 继承父 Agent 的全部权限,权限只增不减
- 出事后只能事后排查日志,无法预防
声明式权限的好处
- 用几行配置精确限定 Agent 可读写的路径
- 规则按声明顺序评估,第一条匹配即生效
- 子 Agent 可以设定独立权限,最小化暴露面
- 与后端策略钩子配合,实现审计日志和内容检查
开始之前
前置知识
- 了解 Deep Agents 的基本架构(已完成第 1-9 章)
- 熟悉
createDeepAgent的基本用法 - 了解内置文件系统工具:
ls、read_file、write_file等 - 了解 Glob 模式的基础语法(
**、*)
读完后你应该能
- 用
FilesystemPermission规则限制文件访问 - 理解"首次匹配生效"的评估机制
- 为子 Agent 设置独立的权限策略
- 在 CompositeBackend 中正确使用权限
- 知道何时用权限、何时用后端策略钩子
适用范围
- 权限仅适用于内置文件系统工具
- 自定义工具和 MCP 工具不受权限控制
- 沙箱后端的
execute工具不受限制 - 需要
deepagents>=1.9.1
ls、read_file、glob、grep、write_file、edit_file)。如果你需要控制自定义工具或做内容检查,请使用后端策略钩子。
基本用法
用几行代码禁止 Agent 写入任何文件
将 FilesystemPermission 规则列表传递给 createDeepAgent 的 permissions 参数。规则按声明顺序评估,第一条匹配的规则生效。如果没有任何规则匹配,操作被允许。
const agent = createDeepAgent({
model,
backend,
permissions: [
{
operations: ["write"], // 只限制写操作(read 不受影响)
paths: ["/**"], // /** 匹配所有路径
mode: "deny", // 拒绝该操作
},
],
});
// Agent 现在只能读取文件,不能写入或修改任何文件
宽松默认值
不传 permissions 参数时,Agent 的所有文件系统操作默认全部允许。这适合快速原型开发。
显式声明
一旦传入权限规则,只有匹配 mode: "allow" 的操作才被允许。建议在生产环境中始终显式声明权限。
规则结构
每条 FilesystemPermission 包含三个字段
| 字段 | 类型 | 说明 |
|---|---|---|
operations |
("read" | "write")[] |
"read" 涵盖 ls、read_file、glob、grep;"write" 涵盖 write_file、edit_file |
paths |
string[] |
Glob 模式匹配文件路径。支持 ** 递归匹配和 {a,b} 交替匹配 |
mode |
"allow" | "deny" |
匹配时是允许还是拒绝。默认为 "allow" |
operations 详解
| 操作值 | 覆盖的工具 |
|---|---|
"read" | ls、read_file、glob、grep |
"write" | write_file、edit_file |
paths 规则
- 必须是绝对路径(以
/开头) - 不能包含
..或~ - 无效路径会在构建时抛出错误
- 示例:
["/workspace/**"]、["/workspace/.env"]
paths: ["src/**"] 会直接报错,正确写法是 paths: ["/workspace/src/**"]。另外 .. 和 ~ 也不支持——用明确的绝对路径代替。
隔离工作区
只允许 Agent 在指定目录下读写
最常见的场景:把 Agent 限制在一个工作区目录内,拒绝所有外部访问。这是"白名单 + 兜底拒绝"的经典模式。
const agent = createDeepAgent({
model,
backend,
permissions: [
// 第 1 条:允许在 /workspace/ 目录下读写(具体允许在前)
{
operations: ["read", "write"],
paths: ["/workspace/**"], // ** 匹配任意层级的子目录
mode: "allow",
},
// 第 2 条:拒绝所有其他路径的读写(兜底规则)
{
operations: ["read", "write"],
paths: ["/**"], // 捕获所有未被前面规则匹配的路径
mode: "deny",
},
],
});
/workspace/** 的拒绝会先匹配到,允许规则永远不会生效。
保护敏感文件
拒绝访问特定文件,允许其余工作区操作
实际开发中,工作区里总有几个敏感文件:.env、密钥目录、示例数据等。你需要允许 Agent 读写工作区,但排除这些危险路径。
const agent = createDeepAgent({
model,
backend,
permissions: [
// 第 1 条:拒绝访问 .env 和 examples 目录(具体拒绝在前)
{
operations: ["read", "write"],
paths: ["/workspace/.env", "/workspace/examples/**"], // 精确匹配敏感路径
mode: "deny",
},
// 第 2 条:允许工作区其余部分的读写
{
operations: ["read", "write"],
paths: ["/workspace/**"],
mode: "allow",
},
// 第 3 条:拒绝所有工作区以外的访问
{
operations: ["read", "write"],
paths: ["/**"],
mode: "deny",
},
],
});
错误顺序
// 先允许 /workspace/**
// 再拒绝 /workspace/.env
// → .env 的拒绝永远不会被评估
// 因为 /workspace/** 先匹配了!
正确顺序
// 先拒绝 /workspace/.env
// 再允许 /workspace/**
// → .env 被拒绝,其余被允许
规则顺序对比
顺序决定一切——用一个完整的正反例来说明
// ✅ 正确:具体规则在前,宽泛规则在后
const correctPermissions: FilesystemPermission[] = [
// 先拒绝敏感文件
{ operations: ["read", "write"], paths: ["/workspace/.env"], mode: "deny" },
// 再允许工作区
{ operations: ["read", "write"], paths: ["/workspace/**"], mode: "allow" },
// 最后兜底拒绝
{ operations: ["read", "write"], paths: ["/**"], mode: "deny" },
];
// ❌ 错误:宽泛规则在前,具体规则被"遮蔽"
const incorrectPermissions: FilesystemPermission[] = [
// 先允许工作区——.env 也被匹配了!
{ operations: ["read", "write"], paths: ["/workspace/**"], mode: "allow" },
// 这条拒绝永远不会对 .env 生效
{ operations: ["read", "write"], paths: ["/workspace/.env"], mode: "deny" },
// 兜底拒绝
{ operations: ["read", "write"], paths: ["/**"], mode: "deny" },
];
| 请求路径 | 正确顺序结果 | 错误顺序结果 |
|---|---|---|
/workspace/.env | deny | allow(危险!) |
/workspace/src/index.ts | allow | allow |
/etc/passwd | deny | deny |
只读记忆
让 Agent 读取记忆但不能修改——组织级策略的典型场景
想象一下:公司维护了一个共享知识库,所有 Agent 可以查阅,但只允许应用代码更新。用权限规则轻松实现:
const store = new InMemoryStore();
const agent = createDeepAgent({
model,
backend: new CompositeBackend(new StateBackend(), {
"/memories/": new StoreBackend({
namespace: (rt) => [rt.serverInfo.user.identity],
}),
"/policies/": new StoreBackend({
namespace: (rt) => [rt.context.orgId],
}),
}),
permissions: [
// 禁止写入记忆和策略目录
{
operations: ["write"],
paths: ["/memories/**", "/policies/**"],
mode: "deny",
},
],
store,
});
// Agent 可以读取 /memories/ 和 /policies/ 下的内容
// 但不能修改它们
适用场景
- 组织范围的知识库——全员可读,仅管理员可写
- 共享的策略文件——Agent 可以参考但不能篡改
- 只读的参考文档——防止 Agent 意外覆盖
为什么不直接用 mode: "allow"?
- 默认就是全部允许,所以只需要显式拒绝写入
- 读取操作不受影响,保持默认允许
- 简单、明确、不容易出错
完全禁止访问
阻止所有读写——最严格的基线配置
某些场景下你需要一个"什么都做不了"的 Agent 作为起点,然后根据需要逐步放开权限。
const agent = createDeepAgent({
model,
backend,
permissions: [
{
operations: ["read", "write"], // 同时限制读和写
paths: ["/**"], // /** 匹配所有路径
mode: "deny", // 拒绝所有读写操作
},
],
});
// Agent 的文件系统工具完全不可用
// 可以在此基础上叠加 allow 规则逐步放开
什么时候用
- 审计 Agent:只需要审查代码,不需要读写文件
- 分析 Agent:只需要接收输入参数,不需要文件系统
- 作为子 Agent 的安全基线
如何逐步放开
permissions: [
// 在 deny all 之前
// 插入具体的 allow 规则
{
operations: ["read"],
paths: ["/workspace/**"],
mode: "allow",
},
// 兜底拒绝
{
operations: ["read", "write"],
paths: ["/**"],
mode: "deny",
},
]
子 Agent 权限
子 Agent 默认继承父 Agent 权限,但可以完全替换
子 Agent 的权限机制有三个要点:
默认继承
如果不设置 permissions,子 Agent 完全继承父 Agent 的权限规则。
完全替换
在子 Agent 规范中设置 permissions,会完全替换(不是合并)父级规则。
空数组 = 无限制
设置 permissions: [] 表示无任何限制。省略则继承父级。
const agent = createDeepAgent({
model,
backend,
permissions: [
{ operations: ["read", "write"], paths: ["/workspace/**"], mode: "allow" },
{ operations: ["read", "write"], paths: ["/**"], mode: "deny" },
],
subagents: [
{
name: "auditor",
description: "只读代码审查员",
systemPrompt: "Review the code for issues.",
// 完全替换父级权限——只允许读取工作区
permissions: [
{ operations: ["write"], paths: ["/**"], mode: "deny" },
{ operations: ["read"], paths: ["/workspace/**"], mode: "allow" },
{ operations: ["read"], paths: ["/**"], mode: "deny" },
],
},
],
});
permissions 是替换而非合并。如果你想让子 Agent 比父 Agent 权限更严格,需要在子 Agent 的规则中完整声明所有约束。没有"在父级基础上追加"的机制。
组合后端权限
使用 CompositeBackend 时,权限路径必须限定在路由前缀内
CompositeBackend 把不同的路径路由到不同的后端。权限路径必须限定在已注册的路由前缀之下,否则会在构建时抛出错误。
const sandbox = new StateBackend();
const memoriesBackend = new StateBackend();
const composite = new CompositeBackend(sandbox, {
"/memories/": memoriesBackend, // 只注册了 /memories/ 路由
});
// ✅ 正确:路径限定在路由前缀内
const agent = createDeepAgent({
model,
backend: composite,
permissions: [
{ operations: ["write"], paths: ["/memories/**"], mode: "deny" },
],
});
// ❌ 错误:/workspace/ 不在路由范围内
createDeepAgent({
model,
backend: composite,
permissions: [
{ operations: ["write"], paths: ["/workspace/**"], mode: "deny" },
],
// → 构建时报错!
});
// ❌ 错误:/** 匹配所有路径,包含超出路由范围的路径
createDeepAgent({
model,
backend: composite,
permissions: [{ operations: ["read"], paths: ["/**"], mode: "deny" }],
// → 构建时报错!
});
为什么会报错?
沙箱后端通过 execute 工具支持任意命令执行。仅靠路径限制无法防止通过 Shell 命令绕过权限。将权限限定在特定路由后端上可以避免此冲突。
解决方法
确保权限路径对应的目录已被注册为路由。如果需要控制 /workspace/,先把它注册为路由前缀,再设置权限。
权限与策略钩子
知道何时用权限规则、何时用后端策略钩子
| 维度 | permissions 权限规则 | 后端策略钩子 |
|---|---|---|
| 控制范围 | 仅内置文件系统工具 | 所有工具(含自定义和 MCP 工具) |
| 控制维度 | 基于路径的允许/拒绝 | 自定义验证逻辑 |
| 典型用途 | 限制文件读写范围 | 速率限制、审计日志、内容检查 |
| 配置方式 | 声明式 JSON 配置 | 编程式钩子函数 |
| 灵活性 | 中等——路径匹配 | 高——任意代码逻辑 |
用 permissions 的场景
- "Agent 只能在
/workspace/下操作" - "禁止修改
.env文件" - "子 Agent 只能读不能写"
- "隔离工作区,拒绝外部访问"
用策略钩子的场景
- "每次写入操作记录审计日志"
- "每分钟最多执行 10 次文件操作"
- "检查写入内容是否包含密钥"
- "控制自定义工具的访问权限"
速查清单
5 个常用权限模式——直接复制使用
1. 只读模式
permissions: [
{ operations: ["write"],
paths: ["/**"], mode: "deny" },
]
2. 隔离工作区
permissions: [
{ operations: ["read", "write"],
paths: ["/workspace/**"],
mode: "allow" },
{ operations: ["read", "write"],
paths: ["/**"], mode: "deny" },
]
3. 保护敏感文件
permissions: [
{ operations: ["read", "write"],
paths: ["/workspace/.env"],
mode: "deny" },
{ operations: ["read", "write"],
paths: ["/workspace/**"],
mode: "allow" },
{ operations: ["read", "write"],
paths: ["/**"], mode: "deny" },
]
4. 只读记忆
permissions: [
{ operations: ["write"],
paths: ["/memories/**"],
mode: "deny" },
]
本章回顾
权限控制是 Agent 安全的最后一道防线
你学到了什么
- 用
FilesystemPermission声明式控制文件访问 - 规则按声明顺序评估,首次匹配即生效
- 默认策略是"全部允许",需显式添加限制
- 子 Agent 的
permissions是替换而非合并 - CompositeBackend 中路径必须限定在路由前缀内
- 权限控制路径,策略钩子控制逻辑
设计原则
- 默认拒绝:先 deny all,再按需 allow
- 具体在前:敏感路径的拒绝规则放在宽泛允许之前
- 最小权限:只给 Agent 完成任务所需的最少权限
- 深度防御:权限 + 策略钩子配合使用
- 生产必用:任何接触敏感数据的场景都应配置权限