首页
/ Zed Agent 沙箱机制实战:用 OS 级 Sandbox 强制约束 Agent 工具调用的权限边界

Zed Agent 沙箱机制实战:用 OS 级 Sandbox 强制约束 Agent 工具调用的权限边界

2026-09-06 15:22:30作者:董斯意

本文以 Zed 官方文档 docs/src/ai/sandboxing.md 为主体,讲解 Zed Agent 的操作系统级沙箱(Sandboxing)机制:它如何在 terminalfetch 两类工具调用上强制限制文件写入与出站网络、各平台(macOS Seatbelt / Linux Bubblewrap / Windows WSL)的底层实现差异、审批提示与 agent.sandbox_permissions 持久化配置的完整用法,并深入 crates/sandbox 源码剖析 TOCTOU 竞态修复、文件描述符固定与 seccomp 过滤等安全细节,帮助你为自己的 AI Agent 工作环境配置一套可审计、可复用、窄授权的隔离策略。

一、沙箱与工具权限:两道互补的防线

Zed 提供了多种限制 Zed Agent 行为的手段。最直接的是 工具权限(Tool Permissions),但它的局限在于:当 Agent 想执行一段复杂的终端脚本时,逐行审查命令文本几乎不可能。

沙箱(Sandboxing)的思路完全不同:它使用操作系统自身的能力强制限定一次工具调用能够访问的资源,不依赖 Agent 是否遵守任何指令约定。一旦 Agent 试图触碰被沙箱限制的资源,是操作系统直接将其拦截——这是"强制执行"而非"约定遵守"。

两者可以叠加使用,且职责清晰分层:

  • Tool Permissions:限制 Agent 一开始就能否执行某类工具动作(准入层);
  • Sandboxing:当工具动作真正运行起来之后,限制它在 OS 层面能做什么(运行时层)。

需要明确其作用边界:沙箱只作用于 Zed Agent 本身,它不会沙箱化 Zed 编辑器主体、语言服务器(LSP)、扩展、Tasks、你手动的终端标签页,也不会约束 External AgentsTerminal Threads

注意:在某些条件下,Windows 上的沙箱弱于 Linux 与 macOS,可能无法阻止所有逃逸尝试,详见下文 Windows 平台 一节。

被沙箱化的工具

当前 Zed Agent 沙箱只作用于 terminalfetch 两个工具:

工具 沙箱限制的内容
terminal Agent 运行命令时的文件系统写入与出站网络访问;Git 元数据受到保护(只读)。
fetch 可以访问哪些主机(host 白名单)。

其他工具(文件编辑等)仍然受 Tool PermissionsAgent Profiles 和项目信任(project trust)的治理,但它们目前不运行在这个 OS 沙箱内——这一点在评估 Agent 风险面时应当纳入考虑。

从源码结构看,这一分工是明确的:crates/agent/src/tools/terminal_tool.rs 中暴露给模型的沙箱化工具名为 sandboxed_terminal,其输入字段包括 allow_hostsallow_all_hostsfs_write_pathsallow_fs_write_allunsandboxed 以及一段必须填写的 reason(说明为什么该命令需要这些越权),与文档描述的审批提示一一对应。

平台要求

沙箱在各平台都以某种形式受支持。要对 terminal 工具调用做沙箱化,需要:

  • Linux$PATH 上存在一个可运行的、非 setuidbwrap(Bubblewrap)二进制,见 安装 Bubblewrap
  • Windows:WSL 必须可用;
  • macOS:无额外要求(Seatbelt 是系统内置组件);
  • fetch 工具在任何平台都没有额外要求。

crates/sandbox/src/sandbox.rs 中,平台不可用的情形被建模为明确的错误变体(SandboxError::BwrapNotFoundSandboxError::BwrapSetuidRejectedSandboxError::WslUnavailable 等),Zed 据此决定是降级运行并给出警告,还是根本无法沙箱化。

二、默认访问策略:不授予任何额外权限时,沙箱允许什么

默认情况下,被沙箱化的 Zed Agent 工具动作拥有如下权限边界:

访问类型 默认行为
文件系统读取 终端命令可以读取大部分文件系统,包括受保护的 Git 元数据
项目内写入 终端命令可以在打开的项目目录内写入,Git 元数据除外
Git 元数据 .git 目录以及 linked worktree 的 Git 元数据保持可读但不可写
临时文件 终端命令获得一个可写的临时位置;具体行为因平台而异。
其他写入 默认可写位置之外的写入被阻止,除非你批准了一个更宽泛的沙箱请求。
出站网络 默认阻止,除非你批准了针对特定 host 或不受限的网络沙箱请求;host 级精细控制在并非所有平台都可用。
本地 IPC socket 沙箱内命令不能打开 Unix-domain socket(例如连到桌面会话总线的 session bus,或容器守护进程),否则可借此在沙箱外执行命令。

最后一项"IPC socket"限制值得强调。crates/sandbox/README.md 解释了原因:只读 bind mount 并不能阻止进程去 connect() 一个 Unix-domain socket——内核刻意将 socket(以及 FIFO、设备节点)排除在只读文件系统的写检查之外。于是即使 --ro-bind / /,沙箱内命令也可能连上 $XDG_RUNTIME_DIR 下的会话 socket 或 Docker 守护进程,在沙箱外拉起进程,从而同时击穿文件系统与网络限制。Zed 的对策是在 Linux/WSL 上安装一个 seccomp-BPF 过滤器:socket() 仅允许 AF_INET/AF_INET6/AF_NETLINKAF_UNIX(会话 IPC)与 AF_VSOCK(VM 宿主机)一律返回 EPERMsocketpair() 只允许 AF_UNIX;并拒绝 io_uring_*ptraceprocess_vm_*。macOS 侧则不需要这一步,Seatbelt 把 Unix socket 的 connect 当作独立的 network-outbound 能力默认拒绝。

Git 元数据的保护在 Agent 层是如何计算的,可以从 crates/agent/src/sandboxing.rssandbox_git_dirs 函数看到完整实现:它收集每个 worktree 的 .git即使尚不存在也保护,防止命令 git init 后写入新建的元数据)、linked worktree 的 common dir(可能位于 worktree 之外),以及 Git 存储中每个仓库的 dot-git / repository / common 目录。与之对应地,sandbox_worktree_writable_paths 提供"默认可写的项目根"——这两个函数是终端工具和状态 UI 共享的单一事实来源,避免两处漂移。

三、审批提示:Agent 越权时你看到什么

当 Agent 需要默认沙箱之外的访问时,Zed 会在工具动作执行之前弹出沙箱审批提示。根据工具请求的内容,提示可能要求你允许:

  • 访问特定主机的网络,例如 github.com*.npmjs.org
  • 访问任意主机的网络;
  • 对特定文件路径的写权限;
  • 不受限的文件系统写入(Git 元数据除外);
  • 在无沙箱状态下运行该终端命令。

每个请求你可以授予的范围有三种:

  • 仅本次工具动作(one tool action);
  • 当前线程剩余部分(rest of the current thread);
  • 总是(always)。

"线程内"授权只在该线程内被记住;选择"总是"的授权则持久化到 settings.jsonagent.sandbox_permissions 字段下。

从源码看,这一套授权状态由 crates/agent/src/sandboxing.rs 中的 ThreadSandboxGrants 结构承载:它记录线程级授予的 host 模式(含去冗余:*.foo.com 覆盖 api.foo.com)、写路径子树(后授予父目录会自动剪掉被包含的子目录授权)、allow_fs_write_allunsandboxed 标志,以及一个独立的 sandbox_fallback 标志——后者对应"沙箱无法创建时用户确认降级运行"的场景,与"模型主动请求 unsandboxed: true"是两条不同的路径。线程级授权还会序列化进线程的数据库行(to_db / from_db),重启后可恢复。

源码注释中还点出一个容易被忽略的设计决策:持久化的 allow_unsandboxed 与"一次/线程级"的 unsandboxed: true 逃逸是完全不同的两件事。持久化设置会把模型面对的工具直接换成普通(非沙箱)terminal 工具、并从系统提示词中删去沙箱章节;而一次性的模型逃逸请求则保留沙箱化工具与提示词不变,只有那一条具体命令在无沙箱下运行。

四、持久化沙箱权限:agent.sandbox_permissions 完整参考

如果希望预先批准常见的沙箱请求、减少重复弹窗,可以在设置文件中添加持久化权限:

{
  "agent": {
    "sandbox_permissions": {
      "network_hosts": ["github.com", "*.npmjs.org"],
      "write_paths": ["/Users/you/.cache/my-tool"]
    }
  }
}

文档列出的可选项为:

设置项 说明
network_hosts 沙箱化工具可免提示访问的主机。条目可以是精确主机名,或以 *. 开头的子域通配符。
allow_all_hosts 允许沙箱化工具免提示访问任意主机。
write_paths 沙箱化终端命令可免提示写入的目录子树。路径必须是绝对路径。
allow_fs_write_all 允许沙箱化终端命令免提示写入任意位置(Git 元数据除外)。
allow_unsandboxed 彻底关闭 Zed Agent 终端命令的沙箱;fetch 工具也将不受限制。

文档建议:优先使用窄授权(特定主机、特定写路径),而非 allow_all_hostsallow_fs_write_allallow_unsandboxed

对照 crates/settings_content/src/agent.rs 中的 SandboxPermissionsContent 结构,当前仓库的实现还提供了两个文档未展开的辅助开关:

  • warn_confusable_unicode(默认 true):当沙箱升级提示中请求的域名或写路径包含易混淆 Unicode 字符(同形字、不可见字符、双向覆盖控制符)时,显示必须确认的警告——这是对"审批提示欺骗"(prompt-injection 诱导用户批准形近域名)的防御;
  • warn_ntfs_grants(默认 true,仅 Windows/WSL):当授权目标是 Windows 托管(DrvFs)文件系统上的文件时显示警告,因为这类授权的沙箱完整性保证弱于发行版原生文件系统。

另外,write_paths 的条目支持两种形态:裸路径字符串(手写配置),或 {requested, resolved} 对象(Zed 自动写入的形式,resolved 是授权时刻解析出的规范化、无符号链接的目标)。这个设计的意义在下文"TOCTOU 竞态"一节中会展开。

这些持久化设置如何翻译成实际的沙箱策略,可以在 crates/agent/src/sandboxing.rssettings_sandbox_policy 中验证:allow_fs_write_all 映射为 SandboxFsPolicy::Unrestrictedallow_all_hosts 映射为 SandboxNetPolicy::Unrestrictednetwork_hosts 为空则映射为 Blocked,非空则映射为带域名白名单的 Restricted。每条写路径授权都会经过"验证性重开"(verifying reopen),失败的授权会被记录并丢弃(fail-closed),而非静默失效。

五、Git 元数据:沙箱中不可授予的写入

只要终端命令处于沙箱状态,Git 元数据写入就不可被授予。这包括 .git 目录、linked worktree 元数据、refs、index、hooks、本地 Git 配置以及其他 Git 管控的元数据文件。批准某个可写路径或 allow_fs_write_all 都不会使 Git 元数据变为可写

之所以如此严格,crates/sandbox/README.md 给出了理由:对 .git 的写权限可以被升级成沙箱外的代码执行——Git hooks、$EDITOR 等机制都会在沙箱外触发。这也呼应了 SandboxFsPolicy 的设计(见 crates/sandbox/src/sandbox.rs):即便在 Unrestricted 变体中,protected_paths 依然是独立字段,"可写范围再大,受保护路径保持只读"是跨平台不变量。

六、平台实现详解

沙箱在各平台使用不同的操作系统机制。用户看到的审批提示相似,但强制执行的细节各异。

macOS 平台:Seatbelt(sandbox-exec)

macOS 上 Zed 通过 sandbox-exec 使用 Apple 的 Seatbelt 沙箱。

沙箱化终端命令的能力边界:

  • 可以读取文件系统;
  • 可以在打开的项目目录内写入,Git 元数据除外;
  • 可以写入一个每线程独立的临时目录,通过 $TMPDIR$TMP$TEMP 暴露;
  • 可以读取受保护的 Git 元数据,但不能写入——即使你批准了更宽的写权限;
  • 未批准额外路径或更宽写权限时,不能写入其他位置;
  • 未批准网络访问时,不能触网;
  • 只能访问一份 macOS 系统 Mach 服务白名单(开发者工具所需);可能被滥用于逃逸沙箱的服务(LaunchServices 和 launchd,它们能在沙箱外启动进程)、剪贴板(pasteboard)与音频捕获服务均不可达

crates/sandbox/README.md 补充了实现上的一个关键点:Seatbelt 规则的难点在 mach-lookup——它控制着 Launch Services 等能触发沙箱外代码执行的能力,Zed 的 Seatbelt 策略参考了 Codex 与 Chromium 的规则混合而成,具体策略定义在 crates/sandbox/src/macos_seatbelt.rs 中。另有一个 macOS 独有的优势:与 Linux 不同,Seatbelt 在系统调用时刻解析并检查路径,因此后文的符号链接替换攻击(symlink swap)在 macOS 上天然不成立。

主机级网络控制的实现方式:macOS 上批准网络访问后,Zed 会启动一个 HTTP/HTTPS 代理,把访问限制在已批准的主机内。不遵守代理环境变量的工具(SSH、FTP、裸 socket 客户端)即使主机级网络已批准也可能无法工作;对需要联网的终端命令,在可能的情况下优先使用 HTTPS URL 而非 SSH URL

Linux 平台:Bubblewrap(bwrap)

Linux 上 Zed 使用 Bubblewrap(bwrap)做沙箱化,且只使用非 setuid 的 bwrap 二进制。其沙箱完全建立在非特权 user namespace 之上,因此 setuid-root 的 bwrap 不提供任何额外能力,运行它反而意味着执行"参数部分来自模型影响输入"的 root 特权 setup。如果你的 PATH 上只有 setuid-root 的 bwrap,Zed 会拒绝运行它——请安装非 setuid 的 Bubblewrap 以启用沙箱。这一点在代码中有对应:SandboxError::BwrapSetuidRejected("the only available bwrap is setuid-root, which Zed refuses to run")。

沙箱化终端命令的能力边界:

  • 可以读取文件系统,包括受保护的 Git 元数据内容;
  • 可以在打开的项目目录内写入,Git 元数据除外;
  • 可以写入 /tmp——它由一个全新的临时文件系统支撑,并在每次终端工具调用之间清空(注意:当你批准了不受限的文件系统写入时,/tmp 变为真实的宿主 /tmp,而非新临时文件系统);
  • 不能写入受保护的 Git 元数据;
  • 未批准额外路径或更宽写权限时,不能写入其他位置;
  • 未批准网络访问时,不能触网。

与 macOS 相同,Linux 上批准主机级网络访问后走 HTTP/HTTPS 代理限制主机;不遵守代理环境变量的工具(SSH、FTP、裸 socket)同样可能不可用。

若 Bubblewrap 不可用或当前环境无法创建沙箱,Zed 可能在无 OS 沙箱下运行命令,并在工具输出中显示警告(对应 crates/agent/src/sandboxing.rsThreadSandboxGrants::record_fallback 记录的降级授权路径,仅 Linux 与 Windows 平台存在,因为只有 Bubblewrap 沙箱存在"创建失败"的可能)。

警告:在 Linux 与 WSL 上,对文件系统对象的限制是在沙箱创建时确定的。这意味着:一个被授予非 Git 仓库目录 /foo 访问权的 Agent,可以创建 /foo/.git(创建时它尚不存在,因此不受保护)。

安装 Bubblewrap

你需要一个可运行的、非 setuid 的 bwrap 二进制在 $PATH 上。通常从发行版包管理器安装 bubblewrap 即可。可用以下命令验证是否正常工作:

bwrap --ro-bind / / -- echo "working"

"非 setuid" 指 setuid 位。历史上 bubblewrap 同时分发 setuid 与非 setuid 两个二进制;出于安全考虑,setuid 二进制正在被淘汰,因此 Zed 沙箱明确拒绝 setuid 的 bwrap 二进制

Ubuntu 特别要求

注意:以下内容不影响 WSL 上的 Ubuntu。

Bubblewrap 依赖 Linux 内核的 "namespaces" 特性。许多系统允许非特权用户创建 namespace,但该特性历史上被用于各种攻击。作为回应,Ubuntu 23.10 起 Canonical 添加了由 AppArmor 强制执行的、限制非特权 user namespace 的安全措施。

因此,安装 bubblewrap 后你可能还需要为它安装一个 AppArmor profile——一份允许 bubblewrap 无需 sudo 创建 namespace 的配置文件:

sudo apt install bubblewrap

# Ubuntu 25.04 及更新版本,`apparmor` 默认附带 bubblewrap 的 profile。
# 请确保系统已更新
sudo apt install --only-upgrade apparmor

# 旧版本需手动安装 profile
sudo apt update
sudo apt install apparmor-profiles apparmor-utils
sudo install -m 0644 \
  /usr/share/apparmor/extra-profiles/bwrap-userns-restrict \
  /etc/apparmor.d/bwrap-userns-restrict
sudo apparmor_parser -r /etc/apparmor.d/bwrap-userns-restrict

Windows 平台:WSL 沙箱与 NTFS 问题

Windows 上,Zed Agent 的沙箱化仅在 Agent 动作运行于 WSL 内时受支持

警告:受 WSL 实现局限影响,当用户对位于 NTFS 驱动器的路径(包括存储在 NTFS 上的当前项目授权)授予写权限时,终端命令可能写入沙箱授权范围之外。Zed 发生时显示警告。实践中 Zed 认为该问题难以被利用,但无法提供安全保证

Zed 之所以在 WSL 内使用 Linux 的 Bubblewrap 沙箱,是因为 WSL 提供了 Bubblewrap 所需的 Linux 进程与文件系统原语;原生 Windows 进程目前没有同等的沙箱集成,无法以相同方式被 Zed Agent 的 OS 沙箱约束。

在 WSL 内运行时,Linux 的沙箱行为同样适用(包括 bwrap 不得 setuid-root 的要求):

  • 文件系统隔离由 Bubblewrap 提供;
  • 受保护的 Git 元数据保持可读、写入被阻止;
  • /tmp 对沙箱化终端调用是临时的;
  • 网络访问是全有或全无,而非按主机区分——主机级网络请求会被拒绝,需要网络时 Agent 必须请求不受限网络。这一差异在源码中有直接对应:crates/sandbox/src/sandbox.rsresolve_restricted_network 在 Windows 目标上直接返回 UnsupportedPolicy("restricted host network access is not yet supported for Windows sandboxes")。

若未安装 WSL,或你选择无沙箱运行命令,Zed 回退到标准终端行为:在你的原生 shell 中运行。shell 选择顺序为:已安装 bash(scoop 的 bash 或 Git Bash)时优先,否则 PowerShell,最后 cmd.exe。由于命令改而对原生 Windows 路径(C:\.../c/...)而非 WSL 的 Linux 文件系统(/mnt/c/...)运行,路径约定相应变化——为沙箱化 WSL shell 编写的命令可能行为不同。

为什么 NTFS 上的对象会削弱沙箱?

文件系统沙箱的安全性依赖两件事匹配

  • 用户审批时被展示的路径;
  • 实际被授予访问的文件系统对象。

若用户以为授予的是 /foo/hello,实际授予的却是 /bar/world,沙箱即告失败。

在 Linux 上,"文件系统对象"称为 inode。但 /foo/hello 并不直接指代 inode——它大致指代"某个 inode 可能存在的位置":同一时刻指一个 inode,之后可能指另一个。由于符号链接的存在,一个对 /foo 有写权限的 Agent 可以把 /foo/bar 改指向 /secret(即使它没有 /secret 的写权限)。于是,即便 Agent 拥有 /foo 的访问权,我们也不能顺带授予 /foo/bar——那将等于授予 /secret

这意味着我们只能引用满足以下条件的路径:

  • 规范化的(不含符号链接);
  • 绝对路径;
  • 自校验以来未发生变化。

非 WSL 的 Linux 上,我们只需打开该路径拿到一个"文件描述符"(FD)——FD 直接指代 inode,持有 FD 即"固定"(pin)了正确的 inode;对 Linux 文件系统内的对象,这在 WSL 中同样成立。

问题出在 WSL 允许访问 Windows 驱动器上的文件:C:\foo\bar.txt 可从 Linux 以 /mnt/c/foo/bar.txt 访问,机制类似网络驱动器。对这些路径,inode pinning 不保证有效——我们固然能固定 /mnt/c/foo/bar.txt 的 inode,但并未固定其底层的 Windows 文件系统对象,它可能在沙箱存活期间发生变化,从而允许写入逃出沙箱边界。

这个风险在实践中有多大?

在 Zed 的测试中,团队未能利用该问题逃出沙箱。

实践中映射往往相当稳定:Linux 侧生成的 inode 派生自"文件引用"(粗略说是"Windows inode"),其稳定性与 Linux inode 类似;标准的"替换子组件"攻击会产生不同的 inode 编号,因此沙箱内的检查会 fail-closed(拒绝执行)。

但关键在于:这一行为没有文档保证。这是测试中观察到的行为,但 Zed 未能找到覆盖所有 Windows/WSL 版本、所有配置选项的文档来保证它。

Zed 沙箱的设计目标是即使面对一个有动机、且完全控制你项目文件的攻击者,也保持不可破坏;它假设一个"大体善意但偶尔粗心"的 Agent。因此,对上述弱化情形,Zed 无法给出与标准场景同等强度的保证。即便如此,弱化的保证仍然比完全没有沙箱安全得多,值得保持开启。

七、源码纵深:TOCTOU 竞态、FD 固定与策略合并

文档层面描述的"沙箱创建时定权",在 crates/sandbox/README.md 中被展开为一个完整的攻防分析,这里是值得开发者深入阅读的仓库材料。

攻击场景(TOCTOU):假设恶意项目内含恶意指令文件,用户已授权项目内两个嵌套目录 projectproject/cache 可写。Zed 在把路径交给 bwrap 前会检查它不是越界符号链接,但检查与挂载之间存在时间差。攻击者可令一个子代理用 renameat2(2)RENAME_EXCHANGEproject/cache 换成指向 /home/alice 的符号链接,另一个子代理随后写 project/cache/.bashrc——由于 bwrap 挂载时重新解析符号链接,写入最终落在 /home/alice/.bashrc。"检查时是子目录,绑定时是指向别处的符号链接",项目级授权就此升级为任意路径写入。

朴素修复(禁用嵌套授权)为什么不行:它要求全系统任何时刻都不存在祖先-后代的写授权对(跨多个 Zed 窗口也无法保证),且会破坏"父目录可写 + 某子目录只读 + 孙目录可写"这类合理模式。

正确修复:以 FD 为事实来源。bwrap 虽支持 --bind-fd,但 FD 如何进入 bwrap 进程?Zed 选择方案二:

  1. 为每个可写路径打开 O_PATH FD(固定 inode,但不授予内容读写);
  2. 建立 SCM_RIGHTS socket 把 FD 送入沙箱内的 helper 进程;
  3. 执行 bwrap --bind /path1 /path1 ... -- zed --zed-linux-sandbox-launcher <不可信命令>
  4. 沙箱内桥接进程对每个可写 bind:fstat(fd)(device, inode)lstat(mount) 取对应值,逐一比对;全部匹配才执行命令,否则拒绝。

隐藏缺陷与补全修复:最初的实现存在"捕获环节的循环性"——每次运行都重新打开路径取 FD 并反推 bind 路径,若捕获前符号链接就已被替换,open 会固定攻击者选择的 inode,readlink 又报告攻击者的路径,沙箱内校验两侧"一致地错"。完整修复是把规范化路径在授权时刻持久化:授权时解析出无符号链接的规范目标并持久化(即设置中 write_paths{requested, resolved} 形态);执行时用 O_PATH | O_CLOEXEC | O_NOFOLLOW 打开该规范路径,拒绝符号链接叶子(S_IFLNK 检查),并要求 readlink(/proc/self/fd/N) == persisted canonical,任何组件在授权后变为符号链接都会导致 fail-closed。宿主侧的"挂载前 readlink 检查"与沙箱内的"挂载后 fstat/lstat 比对"组合起来覆盖了所有时间窗。由于 FD 无法跨进程重启存活,即使是"本线程"的临时授权也以字符串形式持久化并在重开时重新验证;requested 路径仅用于展示与溯源,永不回灌进执行逻辑。

这套机制在类型系统中落地为 HostFilesystemLocation(见 crates/sandbox/src/util/host_filesystem_location.rs):一个不 Deref、不能以字符串重建的不透明类型,Linux 上携带 {O_PATH fd, canonical_path, untrusted_raw_path},macOS 上携带 {canonical_path, untrusted_raw_path}capture(raw) 用于项目 worktree 根等可信位置与授权时刻解析用户请求路径,reopen(raw, canonical) 用于从持久化授权重建并证明当前对象就是被批准的那个。

策略合并语义同样值得了解。crates/sandbox/src/sandbox.rsSandboxPolicy::merge 定义了"持久设置 + 线程授权 + 单次请求"三层如何叠加:写路径与主机白名单取并集(并做最小覆盖化简,去掉嵌套冗余项),受保护路径永远并集保持保护;Unrestricted 覆盖 RestrictedThreadSandbox::Unsandboxed 在合并中压倒一切。"宽权限但真实存在"的沙箱(如 allow_fs_write_all + allow_all_hosts)与"完全没有沙箱"被刻意建模为不同的变体,因为前者仍强制 Git 元数据只读等不变量。

八、我能在多大程度上信任这个沙箱?

开启沙箱能大幅降低多类攻击的风险,但不能完全消除

  1. OS 级特性本身可能有 bug。操作系统的安全特性历史上出过 bug;Zed 的实现经过充分测试,但也可能存在允许提权的 bug(例如让 Agent 写入本应只读的文件)。
  2. 沙箱只强制你请求过的限制。如果 Agent 请求了对主目录的写权限并被批准,沙箱不会(也不应)阻止它往 $HOME/.ssh 加恶意密钥。

因此请谨慎授予权限。你可以在任意时刻悬停线程右上角的挂锁图标查看当前沙箱状态;当 Agent 请求过宽的权限时,拒绝它并要求更小的授权。Agent 请求提权时必须提供 reason,会显示在提示中——先阅读并判断其合理性再批准。

沙箱限制 Zed Agent 的 terminalfetch 工具,对 Zed 的其他部分(语言服务器、内置 Git 客户端、常规终端等)没有任何效果。即使沙箱开启也需保持警惕,恶意或偏离目标的 Agent 可能利用这些侧信道提权,例如:

  • 向你的代码库加入恶意 Rust 过程宏(proc macro),rust-analyzer 会在沙箱外自动执行它;
  • 修改 Makefile 注入恶意脚本,下次你在内置终端运行 make 时在沙箱外执行;
  • Agent 无法写仓库受保护的 .git 目录,但可以在项目下创建一个子模块——其 Git 元数据(包括 core.fsmonitor 之类的配置)完全由它控制,这些元数据随后会由你在常规终端运行 Git 命令时在沙箱外执行;你的 shell 提示符甚至可能每次渲染都执行 Git 命令!

可采取的缓解措施(来自 crates/sandbox/README.md 与文档的一致表述):

  • 禁用会执行项目内用户自定义代码的语言服务器(如 Rust 过程宏);
  • 使用不执行仓库自定义程序的 Git 状态提示符;
  • 运行 git commit 前审查 diff。

但所有这些都改变不了根本原则:沙箱不能替代良好的安全实践,它只是纵深防御策略中的一层。Zed 的默认配置在安全与便利之间寻求平衡,但鼓励你依据自身的安全需求与风险画像调整设置——一个被禁用的沙箱不是有效的沙箱

九、审批决策清单:选择批准什么

审视沙箱提示时,优先选择能让任务继续的最窄权限

  • 目标已知时,批准特定主机而非所有主机;
  • 批准特定写路径而非不受限的文件系统写入;
  • 仅当命令在沙箱内确实无法工作时,才批准无沙箱执行;
  • 对不熟悉的命令使用一次性批准;
  • 仅对预期会重复使用的访问使用线程级或"总是"级批准;
  • 若命令因沙箱阻止而失败,先问 Agent 为什么需要该权限,再决定是否批准更宽请求。

十、小结

Zed 的沙箱机制把"限制 AI Agent"从提示词工程层面下沉到了操作系统层面:macOS 用 Seatbelt 规则文件、Linux 用非特权 Bubblewrap 加 FD 固定的挂载校验与 seccomp 过滤、Windows 借 WSL 复用 Linux 方案。它的工程重心不在"能不能跑",而在关闭那些看起来已关闭的逃逸窗口——TOCTOU 符号链接替换、Unix socket IPC 逃逸、setuid 提权、审批提示的 Unicode 同形字欺骗。理解 agent.sandbox_permissions 的每一档配置(docs/src/ai/sandboxing.md)、默认策略表与"窄授权"原则,再配合 crates/sandbox/README.md 中的安全模型文档,你就能为自己的 Agent 工作环境建立起与自身风险等级匹配的权限边界——同时记住,它是一层防御,而不是全部防御。

登录后查看全文
热门项目推荐
相关项目推荐