# 沙箱与权限 · 把 agent 关进操作系统级的笼子

> 光靠「问一句要不要执行」不够。grok 用 xai-grok-sandbox 在操作系统层面把 agent 关进笼子：Linux 用 Landlock + bubblewrap，macOS 用 Seatbelt，子进程网络用 seccomp 逐个封。这一章拆它的 profile、平台差异、fail-closed，以及沙箱如何让权限层敢少问。

- 作者：David（道雾轩）
- 专栏：Grok Build 源码解读（https://daiw.org/manual/grok-build.md）
- 最后更新：2026-09-15
- 原文：https://daiw.org/manual/grok-build/sandbox-permissions
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

# 沙箱与权限 · 把 agent 关进操作系统级的笼子

一个会跑任意 shell 的 agent，光靠[权限确认](https://daiw.org/manual/llm-to-agent/permissions)问一句「要执行吗」是不够的——模型可能被诱导、命令可能有副作用。grok 的 `xai-grok-sandbox` 把防线下沉到**操作系统层**。

## 内核级、一次性、不可逆

沙箱靠 `nono` crate 落到内核：**Linux 用 Landlock,macOS 用 Seatbelt**。它在**进程启动时应用一次，之后不可撤销**——进程内的文件访问、子进程，统统被罩住。进程级网络保持开放(agent 得连模型 API)，但**子进程网络另外用 seccomp 逐个封**(见下)。

## Profile：几档预设

`ProfileName` 有几档：`Workspace`(默认)、`Devbox`、`ReadOnly`、`Strict`、`Off`、`Custom`。每档 `resolve()` 成一组「可读路径 / 可写路径 / 拒绝路径 / 是否限网」：

| profile | 可写 | 限网 |
| --- | --- | --- |
| `Workspace` | 工作区 + `~/.grok` + 临时目录 | 否 |
| `Devbox` | 除 `/data` 外几乎全部 | 否 |
| `ReadOnly` | 仅 `~/.grok` + 临时 | **是** |
| `Strict` | 工作区 + `~/.grok/sessions` + 临时；读只放行系统目录等白名单，其余默认不可读 | **是** |

`Custom` 可以 `extends` 一个内置档再叠加覆盖。配置从 `~/.grok/sandbox.toml`(全局)+ 工作区的 `.grok/sandbox.toml` 合并，但工作区配置是**只增不改**的——**一个安全属性：工作区不能重定义或掏空全局的 custom profile**。

## 平台差异：一个精妙的 Linux 绕道

这里有个真实的内核限制值得讲：**Landlock 无法「拒绝一个已允许目录树下的子路径」**。所以 grok 在 Linux 上，遇到需要「读拒绝」的 profile，改用 **bubblewrap（`bwrap`）重新 exec 自己**，靠挂载命名空间实现：对拒写路径 `--ro-bind`、对拒读路径 `--ro-bind` 一个 chmod-000 的占位符……然后 re-exec `current_exe`。macOS 那侧 Seatbelt 能直接写拒绝规则，但也有讲究——要为每个具体写子动作(create/unlink/rename…)都写拒绝，还要处理 `/tmp`↔`/private/tmp` 这类 firmlink 别名，防绕过。

## 子进程网络：每个 fork 一道 seccomp

`install_child_network_filter()` **必须在 `pre_exec`(fork 后、exec 前)跑**：一段手写的 seccomp BPF(在父进程里预先建好)，对 `connect/bind/sendto/sendmsg/...` 连同 `io_uring_*` 这类绕路的调用一起返回 `EPERM`、其余放行，再 `PR_SET_NO_NEW_PRIVS` + `PR_SET_SECCOMP`。[上一章](https://daiw.org/manual/grok-build/shell-and-pty)的两条执行路径现在都调 `restrict_child_network(cmd)`——它在 `should_restrict_child_network()` 为真时替命令挂上这个 `pre_exec`。于是限网 profile 下，agent 跑的每条命令都联不了网。

## fail-closed 是底线

整套设计**处处 fail-closed**：遇到无法转义的 Seatbelt 路径、超限的 glob、缺失的 `bwrap`——一律**报错或 `exit(1)`，绝不静默地「没沙箱就裸奔」**。启动时如果需要读拒绝(或 hook 写保护)而 bwrap 起不来，直接 `exit(1)`，并提示去装 bubblewrap。

## 后来补上的几道加固

- **命名空间锁定**：bwrap 里再装一层 seccomp，禁掉 `mount`/`unshare`/`setns` 等调用，防止读拒绝的挂载被改回去；`read_deny_verify.rs` 还会校验读拒绝确实生效。
- **hook 写保护**(`hook_write_deny.rs`)：非 devbox 档不许改写 grok 的全局 hook 来源(`~/.grok/hooks/`、`hooks-paths` 及其登记的目标)，做不到就 fail-closed。
- **容器运行时 socket**(`runtime_sockets.rs`)：限网档启动时屏蔽 docker/podman/containerd 的 API socket——否则子进程能借它们绕过网络限制。

## 沙箱让权限层敢少问

最后一环把两者接起来(`xai-grok-workspace/src/permission/manager/mod.rs`)：打开 `[sandbox] auto_allow_bash`(默认关)后，**沙箱激活时 bash 调用可以被自动放行**(`sandbox_may_auto_allow_bash`)——除非命中「危险命令下限」才还要问你。逻辑很干净：**既然 OS 已经把笼子焊死了，权限层就能安全地少弹确认框**。沙箱不是权限确认的替代，而是它的底座——有了硬隔离，软确认才能松绑。

## 和《从 LLM 到 Coding Agent》对照

对照 [permissions](https://daiw.org/manual/llm-to-agent/permissions)：教学实现的权限是「执行前问一句」。grok 的增量是把安全做成**两层**：上层是可交互的权限确认，下层是不可逆的 OS 级沙箱(Landlock/Seatbelt/seccomp)。这是「玩具靠自觉，产品靠内核」的又一个硬核例子。工具系统还剩几件小而有料的——下一章看 LSP 代码智能。

---

上一篇：[Shell 执行与 PTY](https://daiw.org/manual/grok-build/shell-and-pty) · 下一篇：[LSP 代码智能](https://daiw.org/manual/grok-build/lsp)
