为本地代理实现安全沙箱

编码代理越来越擅长通过运行终端命令来探索环境并进行修改。自动批准这些命令的用户可以解锁功能显著更强的代理,但代价是风险增加。一旦代理出错,就可能删除数据库、发布有问题的代码,或泄露机密。
要求对每条命令进行人工审批可以降低这种风险,但往往只是暂时的。随着审批次数的累积,用户会逐渐不再仔细检查。这在工程师并行运行多个代理、不得不在不同审批提示之间频繁切换时尤为明显。最终会出现“审批疲劳”,从根本上削弱了审批机制本身的意义。
在过去三个月中,我们通过在 macOS、Linux 和 Windows 上推出代理沙箱来解决这一问题。沙箱化的代理可以在受控环境中自由运行,只有在需要跳出该环境时才会请求审批,最常见的情况就是访问互联网。
这大幅减少了打断。与未沙箱的代理相比,沙箱化代理被打断的频率降低了 40%,为用户节省了大量人工审查和审批时间。


我们的沙箱目标
我们着手沙箱设计工作的目标,是在提升安全性的同时尽量消除中断。我们希望在不授予会带来风险的权限的前提下,仍然为代理提供足够的操作空间,让它们保持高效。
要在两者之间取得平衡比想象中更难。许多终端命令即便只是用于基础的测试或构建步骤,也会需要出人意料的高权限。一个过于简单的沙箱会拦截这些命令,从而打断代理的工作流程。设计一个好用的沙箱,本质上是在每个操作系统既定约束下,在安全性与可用性之间不断权衡取舍的过程。
实现
我们提供统一的沙箱 API,但在各个平台上的实现方式不同。macOS、Linux 和 Windows 提供的沙箱原语各不相同,这些差异影响了底层的设计方案。
macOS
我们在 macOS 上评估了四种沙盒方案:App Sandbox、容器、虚拟机,以及 Seatbelt。App Sandbox 是为 Mac App Store 设计的,它要求 Cursor 为代理可能执行的每一个二进制文件签名。这样会显著增加复杂度,并且通过允许代理生成或修改的二进制文件继承 Cursor 的信任,从而引入新的滥用途径。容器会把我们限制在只能运行 Linux 二进制文件,而虚拟机会带来不可接受的启动延迟和内存开销。
因此我们选择了通过 sandbox-exec 访问的 Seatbelt。Seatbelt 于 2007 年引入并在 2016 年被弃用,但仍被 Chrome 等关键的第三方应用程序使用。它允许在一个沙盒配置文件下运行命令,从而约束整个子进程树的行为。
该配置文件通过一种较为特殊的策略语言,以细粒度定义权限,限制系统调用以及对特定文件和目录的读写。我们会在运行时根据工作区级别和管理员级别的设置,以及用户的 .cursorignore 动态生成这份策略。
(deny file-write* (regex "^.*\/\\\.vscode($|\/.*)")
)
(deny file-write* (require-all
(regex "^.*\/\\\.cursor($|\/.*)")
(require-not (regex "^.*\/\\\.cursor/(rules|commands|worktrees|skills|agents)($|\/.*)")))
)
(deny file-write* (regex "^.*\\\.code-workspace$"))
(deny file-write* (regex "^.*\/\\\.cursorignore$"))
(deny file-write* (regex "^.*\/\\\.git/config$"))
(deny file-write* (regex "^.*\/\\\.git/hooks($|\/.*)")
)
(deny file-write* (regex "^(/private)?/var/folders/.*-cursor(-[a-z]+)?-zsh($|\/.*)")
)
Linux
相较于 macOS,Linux 既更简单也更棘手。内核通过 Landlock 和 seccomp 暴露了所需的底层原语,但用户态需要负责将它们组合成一个可用的沙箱。虽然有若干开源项目已经有效地组合了这些机制,但没有一个支持诸如 .cursorignore 之类的功能。
我们决定直接使用 Landlock 和 seccomp。Seccomp 阻止不安全的系统调用,而 Landlock 强制执行文件系统限制,使我们可以让被忽略的文件对沙箱内的进程完全不可访问。我们将用户工作区映射到一个 overlay 文件系统中,并用受 Landlock 约束的副本覆盖被忽略的文件,从而使这些文件无法被读取或修改。
在 Linux 沙箱中,查找并重新挂载这些文件是最慢的部分。如果能像 macOS 那样按需惰性过滤文件系统操作会更简单,但 Linux 并没有在 seccomp-bpf 上下文中提供对文件路径的便捷访问。
Windows
在 Windows 上,我们在 WSL2 中运行 Linux 沙箱环境。要构建一个等效的原生 Windows 沙箱要困难得多,因为现有的大多数沙箱基础机制都是为浏览器设计的,并不支持通用开发工具。我们正与 Microsoft 合作,推动这些必要的基础机制得以提供。
教会 Agent 使用沙箱
只有当 Agent 能够预判哪些命令会在沙箱内成功执行,并识别何时需要升级权限时,沙箱才真正有效。让模型具备沙箱感知能力需要对 Agent harness 做出一些改动。
我们首先更新了 Shell 工具的描述,来说明沙箱的约束条件:根据用户设置,命令是否具备对文件系统、git 或网络的访问权限,以及 Agent 在需要时如何请求更高权限。要打磨出一个让我们满意的基础 harness 修改方案,需要大量手动测试:执行一些常见的 rollout,观察哪些地方与预期不符,微调提示词,然后再重复这些 rollout。
接着,我们使用内部基准测试 Cursor Bench 评估这些改动的影响,对比开启和未开启沙箱的 Agent。我们很快注意到一个常见失败模式:Agent 会在不改变权限的前提下,一遍又一遍地重试同一个终端命令。
为了解决这个问题,我们更新了 Shell 工具结果的呈现方式,明确展示导致失败的具体沙箱约束,并在某些情况下建议 Agent 升级权限。发布这些提醒后,Agent 从沙箱相关失败中恢复得更加平滑,离线评测表现也显著提升。
不过,离线评测只能反映整体情况中的一小部分。为了进一步确保沙箱不会降低用户体验,我们在生产环境中逐步灰度上线了沙箱功能。我们从内部和外部收到的反馈,让我们有足够信心正式发布该功能。现在,在受支持的平台上,大约三分之一的请求都在沙箱中运行,我们也已为包括 NVIDIA 在内的众多企业客户完成了接入。
当 Agent 从生成代码跨越到操作生产系统时,提供明确的执行边界至关重要。我们当前的实现,是朝这个方向迈出的一步。展望未来,我们对在其运行环境约束条件下训练出的“沙箱原生” Agent 尤为期待。这类 Agent 可以被赋予直接编写脚本和程序的自由,而不再仅限于调用工具。
如果你对与代码未来相关的深度技术问题感兴趣,欢迎通过 hiring@cursor.com 与我们联系。