Claude Code 2.1.305 加了一个沙箱开关,名字叫 sandbox.allowNestedUserNamespaces。官方 changelog 里的原话是:让 Linux 上的沙箱命令能创建 user namespace,这样 Chromium 和 rootless podman 才跑得起来,代价是这些命令能绕开沙箱的文件保护。这句话后半段才是重点。这篇拆它解决什么、代价到底多大、什么时候才值得开。
我试着在本机复现,卡在 macOS
我第一反应是亲手跑一遍,看它怎么绕过。结果卡在第一步。本机是 macOS,uname 打出来是 Darwin 23.6.0 arm64。而这个开关是 Linux 和 WSL2 专属。我依次试了 3 条命令,把报错都记下来。which claude 返回 not found,claude 没装。which bwrap bubblewrap 2 个都返回 not found,实现沙箱的 bubblewrap 是 Linux 工具,macOS 上没有。最后想用 Docker 起个 Linux 容器验一下,docker run --rm alpine ... 直接返回报错 failed to connect to docker API,docker daemon 没在运行。这 3 条命令的报错原文,我一条没改。
三条路都堵死了。所以这篇的实测,只能到核对这一步。我核对的过程是这样。先在 npm 上查 claude-code 的版本,npm view @anthropic-ai/claude-code version 打出来是 2.1.322。再打开 2.1.305 的 release body,sandbox.allowNestedUserNamespaces 这条就写在 What's changed 的第一条。这说明 2.1.305 已经发了二十多天。下面的结论都来自官方 changelog 和 settings reference 的交叉核对,不是我在 Linux 上跑出来的。我把这个边界先摆出来,比假装测过要诚实。
它解决什么
先讲清楚这个开关为什么存在。Claude Code 的 Linux 沙箱用 bubblewrap 实现,bwrap 自己就是靠创建一个非特权 user namespace 来把命令关起来。问题在于,被关起来的命令如果再想创建一层自己的 user namespace,默认是禁止的。我在 changelog 和 settings reference 里确认了这一点。
有两类工具偏偏就需要这层嵌套。一类是 Chromium 这类浏览器,它自己的进程隔离沙箱要建 user namespace,Playwright、Puppeteer、Cypress 跑无头浏览器都会踩到。另一类是 rootless podman 和 Buildah,rootless 容器需要 user namespace 做用户 ID 映射。这两类在沙箱里一跑就挂,要么把工具加进 sandbox.excludedCommands 让它在沙箱外跑,要么干脆关掉整个沙箱。这个开关就是给这两条路之间加了一个中间档。
代价是文件保护没了
代价要说得具体点。一个进程调用 unshare(2) 创建 user namespace,或者 setns(2) 加入一个,就会在那个 namespace 里拿到完整的 capabilities。持有 CAP_SYS_ADMIN 的进程,能创建 bind mount。这意味着它能重新挂载文件系统,把沙箱给的只读绑定和写保护拆掉。官方 changelog 用了很直白的一句:lets them get around the sandbox's file protections。
所以这个开关不是顺手打开也无妨的兼容项。开了它,你等于默认沙箱里任何命令都可能绕开文件隔离。这跟 excludedCommands 还不一样。excludedCommands 是精确匹配某几条命令放出去,这个开关是一下子对所有沙箱命令开放了绕过能力。这个差别是选择时的关键,别混。
什么时候开,什么时候用替代
我的选型建议很直接。默认保持 false,这是安全态。只有在一个确定要跑 Chromium 或 rootless podman 的项目里,才在项目级 settings 里开,别写进全局用户配置。写进全局等于让所有仓库都失去文件隔离。开了之后长这样:
{
"sandbox": {
"enabled": true,
"allowNestedUserNamespaces": true
}
}
还有两条替代路线要先想清楚。如果你只是偶尔跑一次浏览器,把那条命令临时放沙箱外逐次确认,比永久放开更稳。如果只是 Chromium 起不来,官方文档给的替代是给浏览器加 --no-sandbox 参数,浏览器内容就不带自己的沙箱了,代价同样存在,但范围比开整个开关小。rootless podman 的替代是 excludedCommands 里配 podman,让匹配的命令在沙箱外跑:
{
"sandbox": {
"enabled": true,
"excludedCommands": ["podman *"]
}
}
| 方案 | 作用范围 | 文件保护 | 适用 |
|---|---|---|---|
| allowNestedUserNamespaces 开 | 所有沙箱命令 | 被绕过 | 项目级、确定要跑浏览器或容器 |
| excludedCommands 配 podman | 匹配的命令 | 沙箱外无限制 | 偶尔跑容器 |
| 浏览器 --no-sandbox | 单个浏览器进程 | 浏览器自身无沙箱 | 只跑 Chromium 的场景 |
一句话结论
这个开关的设计是对的,它给了中间档,而不是逼人关掉整个沙箱。但它的默认值也是对的,关着。文件保护是沙箱唯一的硬边界,一旦命令能建自己的 namespace,这条边界就不再可靠。
开它之前,把仓库的信任等级想清楚。自己团队维护、依赖可控的,可以开。刚克隆的第三方仓库、要跑不信任代码的,别开,宁可用 excludedCommands 逐条放。这一句是这篇的结论,前面所有的拆解都在给这一句做铺垫。