第 13 章

权限控制

用声明式规则精确控制 Agent 的文件系统访问范围

从"全权委托"到"最小权限"的安全实践

为什么需要它

你敢让 Agent 直接操作整个文件系统吗?

无权限控制的危险

  • Agent 可以随意读取 .env、密钥等敏感文件
  • 一次幻觉就可能覆盖重要配置或源代码
  • 子 Agent 继承父 Agent 的全部权限,权限只增不减
  • 出事后只能事后排查日志,无法预防

声明式权限的好处

  • 用几行配置精确限定 Agent 可读写的路径
  • 规则按声明顺序评估,第一条匹配即生效
  • 子 Agent 可以设定独立权限,最小化暴露面
  • 与后端策略钩子配合,实现审计日志和内容检查
问题核心:Agent 的内置文件系统工具默认拥有完全访问权。当你的 Agent 接触生产环境或敏感数据时,权限控制就是最后一道防线。Deep Agents 提供的声明式权限系统让你用最少的配置获得最大的安全保障。

开始之前

前置知识

  • 了解 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 被拒绝,其余被允许
调试技巧:如果发现权限规则没有按预期生效,首先检查规则顺序。在代码中把规则当作一个 for 循环来理解——从上到下,第一个匹配就 break。

规则顺序对比

顺序决定一切——用一个完整的正反例来说明

// ✅ 正确:具体规则在前,宽泛规则在后
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/.envdenyallow(危险!)
/workspace/src/index.tsallowallow
/etc/passwddenydeny
黄金法则:从最具体的规则开始,逐渐过渡到最宽泛的规则。就像防火墙规则一样——先写精确的拒绝,再写宽泛的允许,最后用一条"全部拒绝"兜底。

只读记忆

让 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",
  },
]
设计思路:安全领域有个原则叫"默认拒绝"(deny by default)。先拒绝一切,再按需允许——比"默认允许,发现问题再补拒绝"要安全得多。

子 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" },
      ],
    },
  ],
});
注意:子 Agent 的 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/,先把它注册为路由前缀,再设置权限。

安全设计原则:CompositeBackend 的权限限定是一种深度防御措施。它确保每条权限规则都有一个对应的后端来执行,防止出现"规则声明了但没有后端去执行"的安全空洞。

权限与策略钩子

知道何时用权限规则、何时用后端策略钩子

维度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 完成任务所需的最少权限
  • 深度防御:权限 + 策略钩子配合使用
  • 生产必用:任何接触敏感数据的场景都应配置权限
核心结论:权限控制不是可选的加分项,而是生产环境中 Agent 安全的底线。花 5 分钟配置权限规则,可以避免日后数小时的安全事故排查。从今天起,养成"创建 Agent 时第一件事就设权限"的习惯。

延伸阅读

继续本课程

官方文档

相关概念