Linux:先建视图,再上 seccomp,最后 exec
同一条读取,Mac 上回 Operation not permitted,Linux 上回 No such file or directory。差别在于 Linux 先换进程能看见的文件树,再收紧它能调用的系统接口。
- 读入权限档案并做 WSL1 预检manager.rs L413
- 拼出 helper argv,加上权限档案 JSONlandlock.rs L23
- 外层 run_main 决定走 bwrap 还是捷径linux_run_main.rs L152
- 拼内层命令,带上 --apply-seccomp-then-execlinux_run_main.rs L1520
- bwrap 按顺序 ro-bind、bind、unsharebwrap.rs L306
- exec bwrap 时插入 --as-pid-1launcher.rs L39
- 内层 capget,确认能力已清零linux_run_main.rs L216
- 打开 PR_SET_NO_NEW_PRIVS,然后装 seccomplandlock.rs L61
- fork 用户命令,父进程 waitpid 收孤儿linux_run_main.rs L243
- execvp 接管进程镜像linux_run_main.rs L1565
同事在 Mac 上跑 Codex,agent 去读 ~/.ssh/id_rsa,Seatbelt 立刻回 Operation not permitted。同一份仓库放到 Linux 笔记本上,常见文案变成 No such file or directory。路径常常根本不在挂载里。
如果先打开 PR_SET_NO_NEW_PRIVS,再去调系统自带的 bwrap,不少发行版上的 setuid 二进制会直接起不来。沙箱在装得最全的机器上反而失败。seccomp 是一套内核过滤器,用来规定进程还能调用哪些系统接口;no_new_privs 是它的前置条件,也会挡住 setuid 提权。
默认路径两次进入同一份 helper。外层只拼 bubblewrap,把文件系统换成默认只读,再叠可写根,.git、.agents、.codex 即使落在可写根里也保持只读。内层才打开 no_new_privs、装网络 seccomp,然后 fork、把进程镜像让给用户命令。
函数头把顺序写成三步:
/// Entry point for the Linux sandbox helper.
///
/// The sequence is:
/// 1. When needed, wrap the command with bubblewrap to construct the
/// filesystem view.
/// 2. Apply in-process restrictions (no_new_privs + seccomp).
/// 3. `execvp` into the final command.
openai/codex,核对文件 codex-rs/linux-sandbox/src/linux_run_main.rs,commit 4f39251a01,核对日期 2026-08-22。代码块保留源码原文,这三步就是默认路径的合同。字段注释把原因写在旗标上:bubblewrap 可能依赖 setuid,必须先建好视图,再收紧。出处:codex-rs/linux-sandbox/src/linux_run_main.rs 第 117 至 121 行;codex-rs/linux-sandbox/src/landlock.rs 第 57 至 65 行
能看见什么,和能调用哪些 syscall,是两道墙。先换世界,再收紧调用。换成别的语言重写,发行版上只要还有 setuid 包装器,这个顺序就还成立。
bubblewrap 和 seccomp 必须发生在即将变成用户命令的那个进程里。主 CLI 自己 unshare 再 exec,失败会把整次会话带走,也没法在 PID namespace 里留下一个收孤儿的 1 号进程。超时和取消会留下睡眠进程。
Linux 看 argv[0] 的 basename。文件名已经是 codex-linux-sandbox 就保留,否则改成这个别名,把同一份二进制拐进 helper。外层 exec 的不是 ls,是 helper 自己,带着 --apply-seccomp-then-exec。launcher 还会插进 --as-pid-1,让 bubblewrap 自己当命名空间里的 1 号进程。
内层先 capget。effective 或 permitted 任一非零,立刻 panic,命令不会跑。过了这道检查,才 fork。子进程 execvp,父进程 waitpid(-1) 把子孙都收掉,退出码原样传出。出处:codex-rs/linux-sandbox/src/linux_run_main.rs 第 216 至 221 行、第 242 至 243 行;codex-rs/linux-sandbox/src/launcher.rs 第 38 至 39 行
独立辅助进程加先包装、再收紧,是跨平台同一类自我调用。Windows 看隐藏参数,Linux 看 arg0。包装器失败时,固定退出码加固定 stderr 前缀更好自动归因。
类型名还叫 LandlockCommand,crate 目录也叫 linux-sandbox,网上不少资料会写成 Codex 在 Linux 上用 Landlock。当前默认文件系统沙箱已经是 bubblewrap。Landlock 是显式打开的 legacy 回退,特性开关标成 Deprecated,默认关。legacy 吃不下受限读,也吃不下拆分出来的嵌套只读。
如果 bwrap 失败后静默换 Landlock,模型会按「我被关在视图里」规划,实际却还看得见主机文件树。下一步就会分叉。
外层写明 never falls back。bubblewrap 失败就失败。WSL1 在需要 bubblewrap 时,transform 返回 Wsl1UnsupportedForBubblewrap,命令不会进 helper。全盘可写且不走代理时,根本不会进 bwrap,WSL1 可以过。user namespace 建不出来给启动警告,不会改走更弱的后端。出处:codex-rs/linux-sandbox/src/linux_run_main.rs 第 290 至 294 行;codex-rs/sandboxing/src/manager.rs 第 413 至 420 行、第 696 至 710 行;codex-rs/features/src/lib.rs 第 1064 至 1069 行
跨平台能力不对等时,最容易犯的错是用「我们有 Linux 沙箱」覆盖所有 Linux。本机有新 bwrap、CI 容器禁了 user namespace、同事还在 WSL1,三台机器不是同一道墙。产品文案和模型说明必须按实际启用的那一层来写。
DeepSeek Harness:探得过就换档,合成一拍
DSH 的 Linux 链写死为 bwrap 再 landlock。bwrap 档是视图加 exec,没有 seccomp。探不过才走 landlock-run:在自己身上装规则,再 exec 目标命令。文件系统限制和 exec 合在同一份 main 里。这条路径没有 setuid bwrap,所以不必把过滤器再拆到下一拍。
两边都走独立辅助进程。DSH 启动器失败是 125,还要同时看到 landlock-run: 致命行,更好自动归因。Codex helper 失败是 panic 或原样 wait status。两条链都探失败,DSH 拒绝裸跑。它可以换探测过的第二档,换完仍 fail closed。Codex 默认路径失败不回退 Landlock。出处:packages/sandbox/sandbox-local/src/index.ts 第 159 至 166 行;native/landlock-run/packages/entry/src/main.c 第 254 至 261 行
Grok:三拍打在 agent 上,失败可以回退
Grok 的顺序和 Codex 同一条工程理由:先让可能依赖 setuid 的 bwrap 跑完,再在已经进命名空间的进程里收紧。差别在主体。Grok 把三拍打在 agent 进程上,命令只继承结果。视图是补丁式的 --bind / / 再盖 deny 路径,主机树还在。子进程 spawn 时,若需要限网,才在 pre_exec 里装 seccomp。
bwrap exec 失败且不要求 read-deny 时,它警告一句,回退到 Landlock。内置 Profile 的 apply 失败记警告并继续,is_active() 才是真状态。Codex 外层写明 never falls back。出处:crates/codegen/xai-grok-shell/src/config/mod.rs 第 1293 至 1320 行;crates/codegen/xai-grok-sandbox/src/lib.rs 第 181 至 192 行
同一条读取,换开关之后卡在哪
把探针留在读 /etc/shadow,先打开 WSL1。写下预测:停在哪一层,文案是什么。再关掉 WSL1,打开 legacy Landlock,看受限读会不会变成 UnsupportedOperation。
进阶一问:探针换成 connect 外网。为什么默认路径会穿过视图、停在 seccomp,文案从 No such file 变成 EPERM。