Zed Agent 沙箱机制实战:用 OS 级 Sandbox 强制约束 Agent 工具调用的权限边界
本文以 Zed 官方文档 docs/src/ai/sandboxing.md 为主体,讲解 Zed Agent 的操作系统级沙箱(Sandboxing)机制:它如何在 terminal 和 fetch 两类工具调用上强制限制文件写入与出站网络、各平台(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 Agents 或 Terminal Threads。
注意:在某些条件下,Windows 上的沙箱弱于 Linux 与 macOS,可能无法阻止所有逃逸尝试,详见下文 Windows 平台 一节。
被沙箱化的工具
当前 Zed Agent 沙箱只作用于 terminal 与 fetch 两个工具:
| 工具 | 沙箱限制的内容 |
|---|---|
terminal |
Agent 运行命令时的文件系统写入与出站网络访问;Git 元数据受到保护(只读)。 |
fetch |
可以访问哪些主机(host 白名单)。 |
其他工具(文件编辑等)仍然受 Tool Permissions、Agent Profiles 和项目信任(project trust)的治理,但它们目前不运行在这个 OS 沙箱内——这一点在评估 Agent 风险面时应当纳入考虑。
从源码结构看,这一分工是明确的:crates/agent/src/tools/terminal_tool.rs 中暴露给模型的沙箱化工具名为 sandboxed_terminal,其输入字段包括 allow_hosts、allow_all_hosts、fs_write_paths、allow_fs_write_all、unsandboxed 以及一段必须填写的 reason(说明为什么该命令需要这些越权),与文档描述的审批提示一一对应。
平台要求
沙箱在各平台都以某种形式受支持。要对 terminal 工具调用做沙箱化,需要:
- Linux:
$PATH上存在一个可运行的、非 setuid 的bwrap(Bubblewrap)二进制,见 安装 Bubblewrap; - Windows:WSL 必须可用;
- macOS:无额外要求(Seatbelt 是系统内置组件);
fetch工具在任何平台都没有额外要求。
在 crates/sandbox/src/sandbox.rs 中,平台不可用的情形被建模为明确的错误变体(SandboxError::BwrapNotFound、SandboxError::BwrapSetuidRejected、SandboxError::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_NETLINK,AF_UNIX(会话 IPC)与 AF_VSOCK(VM 宿主机)一律返回 EPERM;socketpair() 只允许 AF_UNIX;并拒绝 io_uring_*、ptrace、process_vm_*。macOS 侧则不需要这一步,Seatbelt 把 Unix socket 的 connect 当作独立的 network-outbound 能力默认拒绝。
Git 元数据的保护在 Agent 层是如何计算的,可以从 crates/agent/src/sandboxing.rs 的 sandbox_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.json 的 agent.sandbox_permissions 字段下。
从源码看,这一套授权状态由 crates/agent/src/sandboxing.rs 中的 ThreadSandboxGrants 结构承载:它记录线程级授予的 host 模式(含去冗余:*.foo.com 覆盖 api.foo.com)、写路径子树(后授予父目录会自动剪掉被包含的子目录授权)、allow_fs_write_all、unsandboxed 标志,以及一个独立的 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_hosts、allow_fs_write_all 或 allow_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.rs 的 settings_sandbox_policy 中验证:allow_fs_write_all 映射为 SandboxFsPolicy::Unrestricted;allow_all_hosts 映射为 SandboxNetPolicy::Unrestricted;network_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.rs 中 ThreadSandboxGrants::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.rs 中
resolve_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):假设恶意项目内含恶意指令文件,用户已授权项目内两个嵌套目录 project 与 project/cache 可写。Zed 在把路径交给 bwrap 前会检查它不是越界符号链接,但检查与挂载之间存在时间差。攻击者可令一个子代理用 renameat2(2) 的 RENAME_EXCHANGE 把 project/cache 换成指向 /home/alice 的符号链接,另一个子代理随后写 project/cache/.bashrc——由于 bwrap 挂载时重新解析符号链接,写入最终落在 /home/alice/.bashrc。"检查时是子目录,绑定时是指向别处的符号链接",项目级授权就此升级为任意路径写入。
朴素修复(禁用嵌套授权)为什么不行:它要求全系统任何时刻都不存在祖先-后代的写授权对(跨多个 Zed 窗口也无法保证),且会破坏"父目录可写 + 某子目录只读 + 孙目录可写"这类合理模式。
正确修复:以 FD 为事实来源。bwrap 虽支持 --bind-fd,但 FD 如何进入 bwrap 进程?Zed 选择方案二:
- 为每个可写路径打开
O_PATHFD(固定 inode,但不授予内容读写); - 建立
SCM_RIGHTSsocket 把 FD 送入沙箱内的 helper 进程; - 执行
bwrap --bind /path1 /path1 ... -- zed --zed-linux-sandbox-launcher <不可信命令>; - 沙箱内桥接进程对每个可写 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.rs 中 SandboxPolicy::merge 定义了"持久设置 + 线程授权 + 单次请求"三层如何叠加:写路径与主机白名单取并集(并做最小覆盖化简,去掉嵌套冗余项),受保护路径永远并集保持保护;Unrestricted 覆盖 Restricted;ThreadSandbox::Unsandboxed 在合并中压倒一切。"宽权限但真实存在"的沙箱(如 allow_fs_write_all + allow_all_hosts)与"完全没有沙箱"被刻意建模为不同的变体,因为前者仍强制 Git 元数据只读等不变量。
八、我能在多大程度上信任这个沙箱?
开启沙箱能大幅降低多类攻击的风险,但不能完全消除:
- OS 级特性本身可能有 bug。操作系统的安全特性历史上出过 bug;Zed 的实现经过充分测试,但也可能存在允许提权的 bug(例如让 Agent 写入本应只读的文件)。
- 沙箱只强制你请求过的限制。如果 Agent 请求了对主目录的写权限并被批准,沙箱不会(也不应)阻止它往
$HOME/.ssh加恶意密钥。
因此请谨慎授予权限。你可以在任意时刻悬停线程右上角的挂锁图标查看当前沙箱状态;当 Agent 请求过宽的权限时,拒绝它并要求更小的授权。Agent 请求提权时必须提供 reason,会显示在提示中——先阅读并判断其合理性再批准。
沙箱只限制 Zed Agent 的 terminal 与 fetch 工具,对 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 工作环境建立起与自身风险等级匹配的权限边界——同时记住,它是一层防御,而不是全部防御。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0625
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00