---
layout: post.njk
source: https://yage.ai/share/why-coding-agents-need-sandbox-20260720.html
speaker: yage.ai
title: 为什么 Coding Agent 需要 Sandbox？
date: '2026-07-20'
summary: 文章探讨了为什么 Coding Agent 需要 Sandbox 环境，核心在于区分‘命令启动’的审批和‘后续行动后果’的管理。Sandbox 解决了程序在启动后可能读取配置、加载依赖或派生子进程等复杂操作带来的不确定性风险，它通过限制进程对文件系统和网络资源的访问范围，实现了对Agent行为的精细化控制。
area: tech-engineering
category: ai-application
tags:
  - sandbox
  - agentic-workflow
  - process-isolation
  - permission-management
people: []
companies_orgs: []
products_models:
  - Cursor
  - Claude Code
media_books: []
draft: true
status: evergreen
---

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

围绕这个问题，有几种常见的困惑：

如果只是担心误删文件，为什么不拦截 rm、sudo 等危险命令？

如果命令来自 AI，为什么不让开发者逐条审批？

如果专用工具的参数更清楚，为什么还要给 Agent 开放 Bash？

如果模型愿意配合用户，为什么还需要 Seatbelt、bubblewrap 或虚拟机这样的系统隔离？

如果进程已经关在虚拟机里，为什么还要另外管理 GitHub 凭证、工作区和 snapshot？

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

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

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

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

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

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

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

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

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

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

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

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

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

## Agent
没有恶意，为什么还需要操作系统隔离？

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

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

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

开发工具会执行模型没有写过的代码。
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
写操作，中间至少有三道不同的问题。三者不能互相替代，需要按顺序逐步缩小权限。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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