2-3 权限与沙箱设计

7 阅读4分钟

2-3 权限与沙箱设计


权限层解决什么问题

工具给了 agent 能力,权限层决定这些能力在什么范围内可以行使

两者的区别:工具是"agent 能做什么",权限是"agent 被允许做什么"。一个没有权限约束的 agent,能力越强,一次判断失误的破坏半径就越大。

核心思路来自安全工程的老原则:最小权限(Principle of Least Privilege)——只给完成当前任务必须的权限,其余全收。


Allow-list vs Deny-list

两种策略,适用场景不同:

Allow-list(白名单):明确列出允许的操作,列表之外全部拒绝。

{
  "allow": [
    "Read",
    "Write:src/**",
    "Bash:npm test",
    "Bash:npm run lint"
  ]
}
  • 优点:边界清晰,新增工具默认拒绝,不会因为"忘了禁某个操作"出问题
  • 缺点:维护成本高,任务稍微扩展就要改配置
  • 适合:生产环境、无人值守 loop、高风险操作场景

Deny-list(黑名单):明确列出禁止的操作,其余全部放行。

{
  "deny": [
    "Bash:rm -rf*",
    "Bash:git push*",
    "Write:/etc/**",
    "Write:~/.ssh/**"
  ]
}
  • 优点:灵活,agent 探索空间大,开发调试阶段摩擦小
  • 缺点:容易漏,你永远不知道下一个危险操作是什么
  • 适合:开发阶段、本地沙箱、人在旁边盯着的场景

实践原则:开发阶段用 deny-list 跑通流程,上生产前切成 allow-list。两者不是对立的,可以叠加——先过 allow-list,再过 deny-list,都通过才放行。


多 Agent 场景下的权限边界

单 agent 的权限设计相对简单。一旦引入 orchestrator + sub-agent 结构,权限边界就需要重新设计。

核心问题:sub-agent 应该继承 orchestrator 的权限,还是独立配置?

答案是独立配置,且应该更窄

原因:orchestrator 的职责是拆任务、分配、汇总——它不需要写文件、跑命令。sub-agent 只负责执行具体工作单元,只需要完成该工作单元所需的最小权限。

orchestrator
  ├── 权限:Read、调用 sub-agent
  ├── sub-agent A(代码生成)
  │     └── 权限:Read:src/**, Write:src/**
  └── sub-agent B(测试执行)
        └── 权限:Read:src/**, Bash:npm test

orchestrator 没有写权限,写文件这件事只发生在 sub-agent 层。每一层只拿它那一层需要的权限。

另一个关键点:sub-agent 不应该能提升自己的权限。如果模型可以通过工具调用修改权限配置文件,整个权限体系就形同虚设。权限配置本身应该在 agent 的写权限范围之外。


沙箱设计

权限是逻辑边界,沙箱是物理边界——从操作系统层面隔离 agent 能触达的资源。

为什么需要沙箱:即使权限配置正确,agent 运行的进程仍然继承了当前用户的所有系统权限。一个被注入恶意指令的 agent(prompt injection),可能绕过逻辑权限直接操作文件系统。

常见沙箱手段

Docker 容器是最实用的方案——把 agent 限制在容器里,挂载只读或受限目录,网络访问按需开放:

docker run --rm \
  -v $(pwd)/workspace:/workspace \  # 只挂工作目录
  -v $(pwd)/CLAUDE.md:/CLAUDE.md:ro \  # 配置只读
  --network none \  # 默认断网,按需开
  --memory 2g \
  my-agent-image

文件系统层面,至少要做到:

  • agent 的工作目录和系统目录物理隔离
  • git 仓库的 .git/config、SSH 密钥目录、环境变量文件在挂载范围之外

可观测性:怎么知道 agent 在干什么

权限和沙箱是事前约束,可观测性是事后还原。两者都不能少。

最低要求的三条日志:

每次工具调用记录入参和出参:

{"ts": "2025-05-10T14:23:01Z", "tool": "Write", "input": {"path": "src/utils.py"}, "status": "allowed"}
{"ts": "2025-05-10T14:23:05Z", "tool": "Bash", "input": {"command": "rm -rf tmp"}, "status": "blocked", "hook": "pre_tool_use"}

三个关键问题,日志必须能回答:

  • agent 做了哪些操作,顺序是什么
  • 哪些操作被拦截了,原因是什么
  • 哪一步之后结果开始偏离预期

没有这三条,loop 出了问题只能重跑,无法 debug。

实践建议:可观测性不要等出了问题再加。在 harness 搭起来的时候,日志结构就应该定好——入参、出参、时间戳、是否被拦截、拦截原因,每一条工具调用都要有。


本节核心一句话

权限是逻辑边界,沙箱是物理边界,可观测性是事后复盘的底——三者缺一,agent 跑起来就是在赌运气。