AI 编程AI Agent安全与供应链

为什么 Coding Agent 需要 Sandbox?

对经常使用 Cursor 或 Claude Code 的开发者来说,Sandbox 可能已经是每天都会遇到的界面。Agent 准备运行命令时,系统询问是否放进 Sandbox;我们通常看一眼命令,点击允许,然后继续工作。这个动作足够熟悉,背后的问题反而容易被略过:Coding Agent 为什么需要 Sandbox?

围绕这个问题,有几种常见的困惑:

这些问题看似分散,背后都忽略了同一个区别:审批只能判断眼前这行命令是否可以启动,Sandbox 还要处理命令启动后的行动和后果。程序会继续读取配置、加载依赖、派生子进程,最后还可能带着合法凭证修改外部系统。

下面沿一次完整的缺陷修复任务往下看。Agent 会克隆仓库、运行 npm install、修改文件、执行 npm test,最后把分支推回 GitHub。这个过程可以解释 Sandbox 为什么不能只是一张命令黑名单,也可以看清操作系统隔离、外部权限和独立工作区分别解决了什么问题。

审批只能决定是否开始,Sandbox 管的是开始以后

先看一条普通的 npm install。用户批准的是这两个单词,但包管理器启动后还会读取 package.json 和锁文件、下载依赖,并运行软件包声明的生命周期脚本。脚本又可以启动 Node.js、Python、编译器或其他 shell 程序。审批框里没有展开这些后续程序,因为它们要等包管理器读完项目配置以后才出现。

npm test 也一样。真正运行哪些测试、加载哪些插件、创建哪些临时文件,并不写在这行命令里。答案散落在仓库代码、测试配置和依赖中,还可能受到环境变量与本机状态影响。审查启动命令可以判断它是否符合当前任务,却无法仅凭这一行文字推导出完整的执行过程。

面对这种不确定性,常见的替代方案有三种。判断它们是否够用,可以看两个问题:它能否保留 Coding Agent 使用现有工具链的能力,又能否限制未知代码造成的后果。

  1. 建立命令黑名单。 系统可以拦下 rmsudo 等高风险命令,但这只覆盖了已知入口。Python 的文件 API、Node.js 程序和编译脚本仍然可以删除同一个文件。要按名称列完所有等效路径,系统最终还是需要理解每种语言、每个工具和每段项目代码会造成什么结果。

  2. 让人审批每一步。 审批适合少量、意图清楚且影响较大的动作,却不适合每次文件读取、临时写入和子进程调用。一次测试会产生大量底层操作。它们如果都停下来等人确认,Agent 无法连续完成诊断;人也难以判断一个陌生测试插件的某次文件访问是否必要。

  3. 取消 Bash,只开放专用工具。 对只需读取 issue、生成回复草稿的封闭任务,这可能已经足够。Coding Agent 面对的操作集合却不固定:仓库会带来自己的编译器、测试框架、脚本和调试工具。完全改用专用 API,意味着平台要不断重新包装这套命令行生态。

三种方案各自解决了一部分问题。黑名单保留了工具能力,却覆盖不了等效操作;逐步审批保留了人的判断,却无法承受开发工具链的调用频率;专用工具可以缩小权限,却难以容纳开放式的开发任务。Sandbox 采用了另一种取舍:保留代码执行能力,把文件和网络限制放到进程下面执行。

Anthropic 关于使用代码执行与 MCP 的工程分享展示了保留通用接口的好处:Agent 可以通过代码发现和调用工具,也可以把中间结果留在文件里,而不必把每个工具和每份结果都送进模型上下文。Cursor 终端文档则把两个时刻分开:Run Mode 决定命令何时直接运行、何时询问,以及何时进入 Sandbox;Sandbox 再限制已经启动的命令可以访问哪些文件和网络。

因此,Sandbox 不需要证明一段程序正确,也不需要提前列出完整的执行路径。它处理的是程序启动后的访问范围:即使包管理器后来加载了审批框里没有出现的脚本,这些进程仍然受到同一组资源限制。

这解释了审批为什么不能代替 Sandbox。但如果 Agent 本身没有攻击用户的意图,为什么还要用操作系统强制执行这些限制?答案要从命令启动以后,实际参与任务的代码说起。

Agent 没有恶意,为什么还需要操作系统隔离?

一个开发任务并不只有 Agent 在行动。沿着刚才的 npm installnpm test 往下看,风险会从三个地方进入:Agent 自己的判断、它读取的外部内容,以及它启动的第三方代码。

  1. Agent 可能判断错误。 它可能误解目录结构,在清理测试缓存时算错路径;也可能写出范围过大的命令。文件访问限制可以让这类错误停在任务工作区,而不是落到其他项目或用户目录。

  2. 外部内容可能改变 Agent 的判断。 Issue、代码注释和网页本来是它分析问题的材料,其中也可能夹带 prompt injection。Agent 一旦把这些内容误当成用户要求,仍会使用正常的工具和凭证,只是行动目标已经偏离原任务。

  3. 开发工具会执行模型没有写过的代码。 npm install 会运行依赖脚本,构建过程会加载插件,npm test 会执行仓库和第三方包中的代码。这些程序不受模型对齐约束。即使 Agent 自己选对了命令,它调用的工具链仍可能读取任务不需要的文件,或者建立多余的网络连接。

这三种风险最终都会变成进程的文件访问和网络访问。操作系统隔离不必判断错误来自模型、提示词还是依赖包;它只需让 Bash 及其启动的程序遵守同一组规则。

根据 Claude Code 的安全隔离文档,它在 macOS 上使用 Seatbelt,在 Linux 或 WSL2 上使用 bubblewrap,限制 sandboxed Bash 的文件系统与网络访问。这项表述的范围是 Bash 及其子进程,不能据此假定 Claude Code 的每一种工具都处在同一个边界内。

具体策略可以允许进程读写当前任务的工作区,同时拒绝无关目录;也可以开放安装依赖所需的网络地址,而不开放任意外连。这些只是策略可以表达的选择,实际保护效果取决于配置。系统不需要判断一段安装脚本是否善意,只需要限制它能够接触的资源。

任务执行下面临的双重风险:Agent 自身的错误与第三方依赖中的未知代码同时进入隔离工作区,由操作系统隔离限制其访问宿主资源

到这里,操作系统隔离约束的仍是本机上的进程。等本地测试通过,Agent 要把分支推回 GitHub,任务就会越过这条边界:主机可能没有受到影响,一次带有合法凭证的远端写操作却仍然可能超出任务需要。

虚拟机守住了主机,却守不住合法权限

为了推送修复分支,系统必须允许 Agent 访问 github.com。操作系统隔离仍能保护宿主文件,但远端请求已经离开本机;只要请求带有有效凭证,它就可能修改真实的代码库。

从允许联网到允许一次符合任务的 GitHub 写操作,中间至少有三道不同的问题。三者不能互相替代,需要按顺序逐步缩小权限。

  1. 网络策略限制请求去哪里。 系统可以只允许访问 github.com,但域名无法说明请求是在读取 issue、推送当前分支、修改仓库设置,还是调用删除接口。同一个目的地承载了后果完全不同的操作。

  2. 凭证代理减少原始 Token 暴露。 如果把 GitHub Token 放进 Sandbox 的环境变量,依赖脚本和测试代码也有机会读取它。Docker Sandboxes 的安全设计把原始凭证留在宿主机侧;宿主策略允许请求时,代理再加入认证信息。这样可以减少凭证明文进入 Sandbox 的机会。本文没有独立验证这一实现的安全强度。

  3. 外部策略限制这个身份具体能做什么。 隐藏 Token 不等于请求范围已经足够窄。假如宿主策略允许对整个仓库执行写操作,Agent 仍可能发起一项通过策略、却超出当前修复任务所需范围的请求。这里不需要发生逃逸,问题只是合法权限比任务更大。

修复任务通常只需要访问指定仓库、推送当前工作分支,并在有限时间内有效;删除分支或修改仓库设置可以另行确认。策略和调用记录放在 Sandbox 之外,即使内部进程失陷,也不能直接修改这些约束。

这三步的分工由粗到细:域名策略控制网络目的地,凭证代理控制原始秘密是否进入运行环境,外部权限策略控制一次有效身份能够造成哪些远端变化。它们回答了 Agent 能不能把结果推到 GitHub,却还没有回答提交之前的多轮修改应该放在哪里。

文件系统的作用,是把修改留在提交之前

独立工作区承接了提交前的修改。它把 Agent 的尝试留在本地副本中,再让一次代码变更经过三个阶段进入 GitHub。

  1. 先在私有副本中修改。 仓库克隆到独立工作区后,Agent 在本地编辑源码、安装依赖并运行测试。此时改变的是这份副本,不是 GitHub 上的共享分支。一次错误修改可以让测试失败,却不会因为写入文件就自动进入主线。

  2. 再把修改变成可以检查的候选结果。 Git diff 展示具体变更,测试检查已知行为,代码审查判断这份修改是否符合任务。这些步骤不能证明代码没有缺陷,但会在共享之前暴露一部分错误。

  3. 最后通过 commit 或 Pull Request 提交。 到这一步,本地修改才离开工作区,进入团队共享的代码库。提交动作需要前一节讨论的 GitHub 权限,也可能再次等待人工确认。

这三个阶段把尝试和提交分开。某种修复思路走不通,可以撤销文件变化;依赖环境混乱,可以丢弃整个工作区。snapshot 还能保存一个本地状态,方便任务回到此前的文件和环境。回滚针对的是仍然留在工作区里的变化。

同一个文件系统还有第二个用途。源码、编译日志和测试报告往往远大于模型一次需要阅读的内容。Manus 关于上下文工程的分享指出,Agent 可以把这些材料留在文件系统里,在上下文中只保留路径,需要时再读取。文件因此既记录修改结果,也让 Agent 在后续步骤重新检查现场。

不过,文件系统只能恢复文件系统里的状态。已经外传的源码无法通过恢复快照收回,发出的邮件不会消失,远端资源也不会自动重建。本地工作区让代码修改能够检查、丢弃和重做;外部动作仍需在发生前限制权限。

本地工作区的修改验证与外部提交的阶段流向:本地回滚只能撤销工作区内的文件修改,而外部敏感操作与泄露风险必须由事前权限闸门阻断

如果任务始终在同一台虚拟机里连续运行,到这里已经可以完成一次修改和提交。Coding Agent 的任务却经常会停下来等待审查,底层虚拟机可能先于任务结束。此时需要保留的就不只是文件。

Sandbox 隔离的单位,正在从进程变成一次委托

Agent 可能运行测试后等待开发者审查,第二天再根据反馈修改代码。底层虚拟机可以因为超时或资源回收而重建,但这次委托还没有结束。

新实例要继续工作,至少需要接回三类状态。

  1. 工作区状态。 文件快照可以带回源码、依赖和测试产物,但不能恢复旧进程的内存与网络连接。

  2. 任务进度。 系统还要知道任务为什么暂停、哪些测试已经通过、哪些结论需要重新验证。单看文件内容不一定能回答这些问题。

  3. 授权状态。 此前的人工批准是否仍适用于下一步,取决于批准的是哪项动作、面向哪个分支,以及有效期是否结束。这些信息不能只存在已经销毁的虚拟机里。

三类状态需要与同一次任务关联,并独立于计算实例保存。虚拟机更换后,系统才能知道从哪里继续,以及下一项外部动作是否还要等待确认。此时 Sandbox 管理的时间范围已经超过了单个进程。

回到最初的缓存修复任务。Pull Request 提交后,运行测试的虚拟机可以销毁,本地工作区也可以丢弃;进入共享仓库的修改和已经发出的远端请求则会留下。Sandbox 缩小了本地执行能够接触的范围,工作区延迟了修改进入真实代码库的时刻,外部策略限制合法身份能够造成的远端变化。它们共同缩小一次错误委托的后果,却没有替开发者证明代码正确,也没有替用户决定哪些远端变化可以接受。

鸭哥每日手记

日更的深度AI新闻和分析