让 Agent 修改项目、运行测试,也就允许它在我们的电脑或服务器上执行程序。这些程序究竟能碰哪些文件,权限又是谁在控制?

我们用一个小型 Node.js 示例项目把过程走一遍。假设 Alice 把项目放在 /Users/alice/projects/name-cleaner,其中的 slugify() 负责把文章标题转成 URL 中使用的短名称。

这个函数有个小问题:标题中连续出现三个空格时,生成的短名称也带上了三个连字符。

输入:Hello,   World!
期望:hello-world
实际:hello---world

问题出在替换空格的那次 replace() 调用:

export function slugify(input) {
  return input
    .toLowerCase()
    .replace(/[^\w\s-]/g, '')
    .replace(/\s/g, '-')
}

/\s/g 每次匹配一个空白字符,再用 - 替换。例子里的三个空格分别被替换,因此留下了三个连字符。

交给 Agent 的任务是:修改 src/slugify.js,补上测试,再运行 npm test。修复只需把 /\s/g 换成 /\s+/g:加上 + 后,正则会一次匹配一整段连续空白,replace() 再把这一段替换成一个 -。最后检查代码差异(Diff),确认没有无关改动。

供 Agent 读取、修改代码和运行测试的 name-cleaner 项目目录,就是本次任务的工作区(Workspace)。

在项目目录里运行,不代表只能访问项目文件

启动 npm test 时,要把工作目录(cwd)设为 /Users/alice/projects/name-cleaner。这样,npm 才能找到项目的 package.json,并按其中的配置启动测试。

但 cwd 只决定相对路径从哪里开始查找,并不限制程序能访问哪些目录。

为了验证这一点,我们在项目目录之外准备一个不含敏感信息的文件:

/Users/alice/Private/sandbox-probe.txt
内容:sandbox probe

再单独写一个权限测试脚本,尝试读取这个文件:

import { readFile } from 'node:fs/promises'

await readFile('/Users/alice/Private/sandbox-probe.txt', 'utf8')

这个脚本只测试文件访问权限,和 slugify() 的功能测试分开运行。测试前应先确认文件已经创建,而且普通进程确实能读到。否则,读取失败可能只是因为文件不存在,不能据此判断权限限制已经生效。

readFile() 会向操作系统请求打开这个绝对路径。只要进程有读取权限,即使从项目目录启动,也能拿到文件内容。

运行 slugify() 的功能测试需要读取项目文件,却不需要读取 Alice 的 Private 目录。我们希望:即使命令已经获准运行,只要它尝试读取被禁止的文件,系统仍会拒绝。在程序运行期间强制执行文件、网络等资源访问限制的机制,叫作沙箱(Sandbox)。

Workspace 决定修改哪份代码,Sandbox 限制执行能影响什么

命令启动前,先按权限规则处理请求

模型可以请求运行 npm test,但不能给自己的请求放行。负责接收请求、检查权限并启动命令的执行程序,通常叫 Runtime,它是 Agent 应用的一部分。

Runtime 根据工具名、命令、参数、工作目录和已有授权,检查预设的权限规则。结果通常有三种:直接允许、询问用户、直接拒绝。不同 Agent 应用的配置方式不同,并不存在一套通用语法。

例如,在 Claude Code 的项目配置 .claude/settings.local.json 中,可以要求运行测试和创建 Pull Request 前先询问用户。Pull Request(PR)就是把一个分支的修改提交给仓库维护者审查的请求。

{
  "permissions": {
    "ask": [
      "Bash(npm test *)",
      "Bash(gh pr create *)"
    ]
  },
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true,
    "filesystem": {
      "denyRead": ["~/Private"]
    }
  }
}

Bash(...) 指定命令工具,括号内是命令匹配规则;末尾的  * 匹配后续参数,也匹配不带后续参数的 npm test。这是 Claude Code 的配置语法。

Runtime 匹配到 ask 就暂停,展示待执行命令,用户批准后才启动进程。匹配到拒绝规则则直接返回拒绝结果。因此,审批可以发生在启动前,无须等命令运行失败再申请。

以创建 PR 为例,用户批准后,Runtime 才能启动 gh 进程;进程还需能连接 GitHub,并通过 GitHub 的账号和仓库权限检查,PR 才能创建成功。若 Agent 通过连接器或 API 创建 PR,就要检查对应工具的权限,前面的 Bash 命令规则不会自动覆盖这个入口。

JSON 中的 sandbox.enabled 用来启用沙箱,denyRead 则禁止受限进程读取 ~/Private。它没有禁止读取工作区外的所有文件;其他目录是否可读,仍取决于完整配置。

批准决定命令能否启动,Sandbox 限制命令运行后能接触什么

配置怎样变成真正的限制

把 denyRead 写进配置文件,还不会自动限制进程。Runtime 需要读取配置,调用当前操作系统提供的沙箱机制,再带着这些限制启动命令。

Anthropic 开源的 Sandbox Runtime 展示了一种实现:执行程序先初始化 Sandbox,用 wrapWithSandbox() 包装命令,再调用 Node.js 的 child_process.spawn() 启动包装后的命令。

在 macOS 上,它借助操作系统提供的 Seatbelt 沙箱机制,把配置转换成系统可识别的规则。比如“禁止读取 Alice 的 Private 目录”,可以在生成的规则中表达为:

(deny file-read*
  (subpath "/Users/alice/Private"))

这里的 file-read* 指一类文件读取操作,subpath 指这个目录及其下的路径。这只是完整规则中的一个片段;实际生成过程还会处理其他允许项、拒绝项及其重叠关系。

执行程序把生成的规则汇集成一份沙箱策略(profile),交给 macOS 自带的 sandbox-exec 工具。下面是命令结构:-p 接收规则内容,后面指定要运行的 Shell 和命令;尖括号中的内容需要替换成实际值。

sandbox-exec -p <profile> <shell> -c <command>

在 Linux 上,同一项目使用 bubblewrap 准备受限的文件系统和网络环境,再启动程序。由它启动的子进程,也会继承相应限制。

如果系统缺少所需工具,沙箱就可能无法启用。failIfUnavailable 决定此时是否直接报错、停止执行。Claude Code 默认将它设为 false,沙箱不可用时会警告并继续以非沙箱方式执行;本例设为 true,要求沙箱可用后才启动命令。

限制生效后,npm test 可以读取项目文件;权限测试脚本读取 ~/Private/sandbox-probe.txt 时,则会收到权限错误。操作系统检查的是这一次文件访问请求,无须扫描程序源码。

资源规则被转换成平台限制,并在进程访问文件时由操作系统执行

所以不必列出所有读文件的命令:无论用 cat、Python 还是 Node.js,最终都要向系统请求读取。系统按目标路径和操作类型执行同一条限制。

除了文件访问,还需要限制联网。本例测试可以默认断网,需要下载依赖时再允许访问软件源。一种实现是要求进程通过指定代理联网,由代理检查目标域名,同时禁止绕过代理直连。前面的 JSON 没有配置这条网络策略。

这套限制跟着受限进程生效。若 Agent 的文件工具由另一个未受限的进程执行,就需要另外限制它的权限。

读取被拒绝后,任务怎样继续

权限测试脚本请求读取 Private 目录时,操作系统拒绝访问,readFile() 报错。这个脚本没有捕获错误,因此会失败退出。Runtime 收集退出状态和错误输出,再把结果交给模型。

对这个权限测试来说,拒绝读取就是预期结果。修复 slugify() 也不需要访问 Private 目录,所以 Agent 可以继续处理项目内的代码,无须为这次失败扩大权限。

如果另一项任务确实需要被禁止的资源,模型可以提交新的权限申请,说明要访问什么、为什么需要。Runtime 再按策略决定拒绝、要求用户确认或允许重试。确认框出现在处理这份新申请时;之前的文件读取只会返回错误,不会自行获得更多权限。

同一个任务,怎样从本地搬到云端

前面的方案是在 Alice 的电脑上,直接修改 name-cleaner 项目。如果 Alice 还在这个目录里开发,希望 Agent 的改动先放在别处,可以用 Git worktree:从同一仓库检出另一份工作树,让 Agent 在另一个目录和分支上修改。这样分开了代码改动,但两份工作树仍共享部分 Git 数据;文件访问限制依然需要 Sandbox 来执行。

如果连测试所占用的电脑资源也不想承担,就可以把任务交给云端。开发者电脑上的文件不会自动出现在服务器上,因此要先把仓库克隆到远程工作区,再为它准备容器或虚拟机,用来安装依赖和运行测试。

运行位置、代码位置和限制方式是三组可以组合的选择

容器也能在 Alice 的电脑上运行,并非云端专用。无论放在哪里,准备容器时都要决定:让它直接访问已有项目目录,还是给它一份代码副本?

把运行容器的那台机器上的项目目录挂载进去,容器访问的就是这份真实目录。只要允许写入,Agent 的修改就会落到原文件上,删除容器也不会撤销。若把代码复制进去,修改则留在副本里,任务结束前需要把结果保存出来。

确定代码放在哪里后,再按任务需要分配资源。这个修复任务只需写入项目和必要的临时目录,安装依赖后就能断网跑测试。给容器设置内存和 CPU 上限,还能避免异常测试占满整台机器。

准备测试进程时,也要检查交给它的环境变量。这些变量是启动时传入的配置值;如果里面带着 GitHub Token,测试代码也可能读到。npm test 无须登录 GitHub,因此不应收到这类凭证。

代码、权限和运行资源准备好后,Runtime 才启动受限进程,应用修改并运行测试。结束时先终止剩余进程,再保存 Diff、日志和测试结果,最后释放临时环境。Alice 的本地项目通常继续保留;云端副本若不再需要,就可以删除。是否删除取决于后续用途,使用 Sandbox 并不要求用完就销毁工作区。

用两组检查验收这套设计

实际部署时,先运行功能测试、查看 Diff,确认修改符合任务要求。还要单独执行边界检查:

  1. npm test

     在批准前不会启动,批准后可以读取 Workspace 内的源码和测试文件。

  2. 权限测试脚本读取 ~/Private/sandbox-probe.txt 时得到权限错误,文件内容不会进入日志。

  3. 测试进程启动的子进程仍受到相同文件限制。

  4. Sandbox 依赖缺失或初始化失败时,命令直接停止。

参考资料

  • OpenAI — Agent approvals & security
     https://learn.chatgpt.com/docs/agent-approvals-security

  • Claude Code — Configure permissions
     https://code.claude.com/docs/en/permissions

  • Claude Code — Configure the sandboxed Bash tool
     https://code.claude.com/docs/en/sandboxing

  • Anthropic — Sandbox Runtime
     https://github.com/anthropics/sandbox-runtime

  • Anthropic Sandbox Runtime — macOS implementation
     https://raw.githubusercontent.com/anthropics/sandbox-runtime/main/src/sandbox/macos-sandbox-utils.ts

  • Git — git-worktree Documentation
     https://git-scm.com/docs/git-worktree.html

  • Claude Code — Use Claude Code on the web
     https://code.claude.com/docs/en/claude-code-on-the-web

  • GitHub — REST API endpoints for pull requests
     https://docs.github.com/en/rest/pulls/pulls#create-a-pull-request

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐