Claude Code 沙箱怎么配:让它跑的命令写不出工作目录、连不上没批准的网站
沙箱是操作系统替你看住的一道边界:Claude 跑的命令只能写工作目录,联网要过一个只放行指定域名的代理。本站在 Ubuntu 24.04 上从安装开始实测:Ubuntu 上多出来的一步、默认读得到凭据怎么堵、命令在哪几种情况下会跑到沙箱外面,以及一个 Linux 上的坑:沙箱里 git add -A 会失败。
本站已实测,测试日期:2026-10-12。
本页目录(16 节)
这篇教程解决一个问题:想让 Claude Code 少问几句、放手去跑命令,又不想把整台机器交给它,怎么办。
权限模式解决的是“这条命令让不让跑”,靠的是规则和模型的判断。判断会出错,一条看上去无害的命令也可能做了名字以外的事。沙箱补的是另一层:命令已经在跑了,它实际上能碰到什么。这一层由操作系统执行,不取决于模型选了什么命令。
打开沙箱之后,Claude 跑的 shell 命令默认只能写工作目录,联网要经过一个代理,代理只放行你批准过的域名。因为有这道边界兜着,沙箱里的命令可以不用逐条问你。
本站在一台 Ubuntu 服务器上从安装开始走了一遍。
| 实测环境 | |
|---|---|
| 日期 | 2026-10-12 |
| 版本 | Claude Code 2.1.296 |
| 系统 | Ubuntu 24.04,bubblewrap 0.9.0,socat 1.8.0.0 |
| 模型 | Opus 5.5 |
| 连接方式 | 通过酷站AI 中转站,不是直连官方 |
| 示例项目 | 一个只有几个文件的空仓库 |
它管什么,不管什么
沙箱只包着 shell 命令(以及这些命令再启动的进程)。默认的边界,表来自官方文档:
| 默认 | |
|---|---|
| 写 | 只有工作目录、一个按用户分的临时目录、你另外加进来的目录。其中还有一批“受保护的路径”不能写 |
| 读 | 机器上的大部分地方,包括 ~/.ssh 这类存凭据的位置 |
| 联网 | 没有直接出去的路。所有连接走本机的一个代理,代理按域名放行,允许的域名一开始是空的 |
| 环境变量 | 继承 Claude Code 的,包括里面的密钥 |
读和环境变量那两行要特别留意:默认的沙箱防的是“写坏东西”和“随便联网”,并不防它读到你的凭据。后面有一节专门讲怎么堵。
下面这些不在沙箱里,文档列得很清楚:
- Claude 自己的文件工具和联网工具:Read、Edit、Write、WebFetch、WebSearch。它们归权限规则管。
- Claude Code 启动的其他进程:hook 的命令、本地的 MCP 服务器、状态栏命令。它们有你的全部权限。
- 你自己用
!输入的命令。 - 你明确排除的命令,以及失败后不带沙箱重试的命令。这两样后面细说。
平台:macOS 用系统自带的机制,不用装东西。Linux 和 WSL2 要装两个包。原生的 Windows 上没有沙箱,命令直接跑;要用得在 WSL2 里运行 Claude Code。
安装(Linux)
sudo apt-get install bubblewrap socat
bubblewrap 负责文件系统的隔离,socat 负责把网络流量转到沙箱的代理上。Fedora 上是 sudo dnf install bubblewrap socat。
Ubuntu 24.04 多出来的一步
在我们这台 Ubuntu 24.04 上,装完这两个包沙箱仍然起不来。直接运行 bwrap 是这样:
bwrap: setting up uid map: Permission denied
在 Claude Code 里打开沙箱再跑命令,每一条都是:
bwrap: loopback: Failed RTM_NEWADDR: Operation not permitted
原因官方文档写了:Ubuntu 24.04 起,默认的 AppArmor 策略不让普通程序创建用户命名空间,而 bwrap 正靠它来做隔离。先确认你的机器是不是这种情况:
sysctl kernel.apparmor_restrict_unprivileged_userns
返回 0,或者提示没有这个键,跳过这一步。返回 1(我们的机器就是),要给 bwrap 加一个 AppArmor 配置文件。内容照官方文档:
sudo tee /etc/apparmor.d/bwrap > /dev/null <<'EOF'
abi <abi/4.0>,
include <tunables/global>
profile bwrap /usr/bin/bwrap flags=(unconfined) {
userns,
include if exists <local/bwrap>
}
EOF
sudo systemctl reload apparmor
加完之后 bwrap 能正常启动了。
这一步改的是系统的安全设置,动手之前要清楚它的含义:它只对 /usr/bin/bwrap 这一个程序生效,允许它创建用户命名空间,别的程序仍受原来的限制;代价是这台机器上的任何用户都可以通过 bwrap 做到这件事,这正是 Ubuntu 默认想收紧的地方。多人共用的服务器上,先和管理员商量。
打开沙箱
在会话里输入 /sandbox,出来一个面板:
Sandbox Mode Overrides Config
Configure mode
1. Sandbox BashTool, with auto-allow
2. Sandbox BashTool, with regular permissions
3. No Sandbox ✔
Auto-allow mode: Commands will try to run in the sandbox automatically, and attempts to run outside
of the sandbox fallback to regular permissions. Explicit ask/deny rules are always respected.
三个页签:
- Mode:选模式。两种带沙箱的模式,边界完全一样,区别只在要不要问你。auto-allow 是沙箱里的命令直接跑,不问;regular permissions 是沙箱照样有,但每条命令仍然走平常的确认。
- Overrides:命令在沙箱里失败后,允不允许退到沙箱外面重试。
- Config:当前生效的沙箱配置。
按文档的说明,缺了必需的包时,面板上只有一个 Dependencies 页签,列出缺什么。
在面板里选好模式,它会把设置存到项目的 .claude/settings.local.json,只对这个项目生效。我们通过脚本模拟按键没能在面板里选中模式,是直接写的设置文件,效果相同:
{
"sandbox": {
"enabled": true
}
}
写完重开会话,面板里第一项后面有了对勾,说明默认就是 auto-allow。想对所有项目生效,把同样的内容写进 ~/.claude/settings.json。只想这一次用,启动时带参数:
claude --settings '{"sandbox": {"enabled": true, "allowUnsandboxedCommands": false}}'
文档有一条警告要知道:默认情况下,如果沙箱因为缺依赖或平台不支持而启动不了,Claude Code 会不带沙箱照常跑命令。把沙箱当成安全关卡的,要再加一个设置 sandbox.failIfUnavailable: true,让它在这种情况下直接退出。
确认它真的在起作用
官方给了两条检查用的命令,让 Claude 去跑(你自己用 ! 输入的不算,那不在沙箱里):
1)touch ~/sandbox-probe
touch: cannot touch '/home/ccdemo/sandbox-probe': Read-only file system
2)curl -sS --max-time 8 --noproxy '*' https://example.com -o /dev/null -w '%{http_code}'
curl: (6) Could not resolve host: example.com
第一条说明家目录写不了。第二条用 --noproxy 故意绕开代理,结果连域名都解析不了,说明沙箱里的命令除了代理没有别的路出去。
写:哪里能写
让它依次往几个地方写文件:
| 位置 | 结果 |
|---|---|
工作目录里的 inside.txt |
成功 |
$TMPDIR(沙箱里它是 /tmp/claude-1001) |
成功 |
/tmp/f1-outside.txt |
Read-only file system |
| 旁边另一个项目的目录 | Read-only file system |
| 家目录 | Read-only file system |
注意 /tmp 本身写不了,能写的是沙箱给的那个按用户分的临时目录,$TMPDIR 已经指过去了。大多数工具认这个变量,所以不用管。
某个工具确实要往别处写(比如 kubectl 要写 ~/.kube),用 allowWrite 加路径,而不是把这个工具整个排除出沙箱:
{
"sandbox": {
"enabled": true,
"filesystem": {
"allowWrite": ["~/.kube", "/tmp/build"]
}
}
}
这里的路径是普通的写法:/ 开头是绝对路径,~/ 开头是家目录。这和权限规则里 Read、Edit 的路径写法不一样,别混了。allowWrite 我们没有实测。
工作目录里也有写不了的地方
工作目录不是整个都能写。让它往几个特殊的位置写:
| 命令 | 结果 |
|---|---|
echo '{}' > .claude/settings.json |
Permission denied |
echo 'echo hi' > .git/hooks/pre-commit |
Read-only file system |
echo '[alias]' >> .git/config |
Read-only file system |
echo ok > notes.txt |
成功 |
道理很直接:这些都是 Claude Code 或者 git 会去加载、去执行的文件。一条命令如果能改它们,就能给自己加权限,或者塞一个之后会在沙箱外面运行的 hook。所以它们单独受保护,而且文档写明没有办法豁免,allowWrite 也不行。
/sandbox 的 Config 页签会列出完整的清单,在 Denied within allowed 下面。我们这里列了几十条,包括项目和家目录下的 .claude 设置文件、skills、hooks、agents、commands,.mcp.json,以及 .git/hooks 和 .git/config。
Linux 上的一个坑:git add -A 会失败
这是实测中发现的,官方文档的排错一节里没有这一条。
在沙箱里跑 git status,会多出一批仓库里根本没有的文件(节选):
?? .bash_profile
?? .bashrc
?? .claude/
?? .gitconfig
?? .gitmodules
?? .idea
?? .mcp.json
?? .profile
?? .zshrc
?? inside.txt
?? notes.txt
只有最后两个是真的。前面那些是沙箱为了守住“受保护的路径”放在那里的占位,看一下就知道它们不是普通文件:
crw-rw-rw- 1 nobody nogroup 1, 3 Aug 18 11:47 .bashrc
crw-rw-rw- 1 nobody nogroup 1, 3 Aug 18 11:47 .gitconfig
它们只在沙箱里的命令运行时存在。在会话外面看同一个目录,这些都没有;退出会话之后也没有留下。
麻烦在于 git 不认这种文件:
$ git add -A
error: .bash_profile: can only add regular files, symbolic links or git-directories
fatal: adding files failed
也就是说,在 Linux 上打开沙箱之后,git add -A 和 git add . 会失败,暂存区什么都没加进去。
我们试了两个办法:
- 按文件名添加,可行。
git add inside.txt notes.txt正常。 - 把这些名字加进忽略清单,不够。 我们把上面那批名字加进了
.git/info/exclude,git add -A换了一个地方失败:error: .claude/agents: can only add regular files...。占位不止顶层那一批。
所以在 Linux 上开着沙箱让它提交代码,要让它按文件名添加。把这一条写进项目的 CLAUDE.md 比每次提醒省事。macOS 用的是另一套机制,这个现象我们没有条件验证有没有。
读:默认几乎都能读
沙箱里的命令去读工作目录以外的文件:
$ cat ~/demo/f1-secret/token.txt
FAKE-TOKEN-not-a-real-secret
$ head -1 /etc/hostname
vmi3497133
都读到了(那个 token 文件是我们放的假凭据)。默认的沙箱不限制读。
要挡住某个位置,用 sandbox.credentials 声明:
{
"sandbox": {
"enabled": true,
"credentials": {
"files": [
{ "path": "~/demo/f1-secret", "mode": "deny" }
]
}
}
}
同样的两种读法再试一次:
1)Bash:cat ~/demo/f1-secret/token.txt
cat: /home/ccdemo/demo/f1-secret/token.txt: No such file or directory
2)Read 工具读同一个文件
成功,内容:FAKE-TOKEN-not-a-real-secret
命令读不到了,Read 工具照样读得到。 沙箱只管 shell 命令,文件工具不在它里面。要两头都堵,还得加一条权限规则,拒绝 Read 读这个路径,写法见大仓库和 monorepo里“不让它读某些文件”一节。那一篇里测到的是反过来的情况:权限规则挡得住 Read,挡不住 grep -r。两层各管一半,合起来才完整。
文档还提醒:没有内置的凭据清单,只有你列出来的文件和变量才受限制。
联网
允许的域名一开始是空的。这时让它访问一个网站(dontAsk 模式):
curl: (56) CONNECT tunnel failed, response 403
deny network-outbound example.com:443 (user denied)
请求到了代理,代理拒绝了,并且指出了是哪个主机。
加上允许的域名:
{
"sandbox": {
"enabled": true,
"network": {
"allowedDomains": ["example.com"]
}
}
}
| 命令 | 结果 |
|---|---|
curl https://example.com |
200 |
curl https://www.iana.org |
403,deny network-outbound www.iana.org:443 |
ping -c 1 example.com |
socket: Operation not permitted |
ping 失败不是因为域名,而是因为它不走 TCP。文档的说明:UDP、基于 QUIC 的 HTTP/3、ping 这类 ICMP 工具都出不了沙箱;不认代理环境变量的工具(普通的 ssh、大多数数据库驱动)即使主机在允许清单里也连不上。
碰到没批准的域名时,谁来决定
取决于权限模式。表来自官方文档:
| 权限模式 | 连接会怎样 |
|---|---|
Manual、acceptEdits |
弹出来问你 |
| auto | 拒绝,除非这条命令事先列出了要用的主机,并且分类器批准了这份清单 |
dontAsk |
拒绝 |
bypassPermissions |
不问,直接放行 |
auto 模式那一行我们看到了实际的样子。同样的 curl,在交互式会话(auto 模式)里:
● Bash(curl -sS --max-time 10 https://example.com -o /dev/null -w '%{http_code}')
⎿ Error: Exit code 56
curl: (56) CONNECT tunnel failed, response 403
● Bash(curl -sS --max-time 10 https://example.com -o /dev/null -w '%{http_code}')
⎿ 200
⎿ Allowed by auto mode classifier
● 200
注:第一次运行时沙箱拦截了对 example.com 的访问。我在这条命令的放行列表里加上 example.com 后
重新运行,返回 200。
第一次被拦,它把这个主机写进了那一条命令自己的放行清单,重新跑,分类器审过之后通过。文档说明这种放行只对那一条命令有效,不会加进会话或者设置里的允许清单。
要让某些域名免去这一步,就写进 allowedDomains。要彻底不允许清单以外的任何主机,在用户设置里加 "network": {"strictAllowlist": true},这一项我们没有试。
凭据:通过中转站用的人要多做一步
前面说过,默认的沙箱不防读,环境变量也原样继承。对通过中转站用 Claude Code 的人,这意味着两件具体的事。我们只检查了“有没有”,没有让任何凭据的内容出现在输出里。
一,接口的 key 就在每条命令的环境里。 让沙箱里的命令判断那个变量存不存在:
环境里有这个变量
二,存着 key 的设置文件,沙箱里的命令读得到。 只数字节数:
$ wc -c < ~/.claude/settings.json
444
444 字节,和文件的真实大小一样,整个文件都读得到。
也就是说,Claude 跑的任何一条命令(包括它装的依赖里的安装脚本)都拿得到这把 key。沙箱限制了它往外发的域名,这降低了泄露的可能,但只要允许清单里有一个能接收数据的域名,路就是通的。
把两处都声明成凭据:
{
"sandbox": {
"enabled": true,
"credentials": {
"files": [
{ "path": "~/.claude/settings.json", "mode": "deny" }
],
"envVars": [
{ "name": "ANTHROPIC_AUTH_TOKEN", "mode": "deny" }
]
}
}
}
再检查:
环境里没有这个变量
$ wc -c < ~/.claude/settings.json
/bin/bash: line 1: /home/ccdemo/.claude/settings.json: Permission denied
两处都挡住了。Claude Code 自己不受影响,它在沙箱外面,照常读设置、照常用这把 key 发请求。
变量名按你实际用的写:凭据放在 ANTHROPIC_API_KEY 里的,就写那个名字。两个变量的区别见Claude Code 接入中转站。
要注意这只管沙箱里的命令。hook、MCP 服务器不在沙箱里,照样拿得到环境变量。文档给了另一个开关 CLAUDE_CODE_SUBPROCESS_ENV_SCRUB,对所有子进程清掉凭据,我们没有试。
命令什么时候会跑到沙箱外面
打开沙箱不等于所有命令都在里面。三种情况要清楚。
一:失败之后不带沙箱重试
默认允许这样做。命令在沙箱里失败,Claude 可以带一个参数重新运行,这一次不进沙箱。它是留给和沙箱不兼容的工具用的。这一次重试由谁批准,按权限模式定,表来自官方文档:
| 权限模式 | 谁批准 |
|---|---|
Manual、acceptEdits |
你。确认框的标题是 “Bash command (unsandboxed)” |
| auto | 分类器 |
dontAsk |
直接拒绝 |
bypassPermissions |
没有人,直接跑 |
我们看到的实际情况:
-
它不会轻易去用。 让它
touch ~/sandbox-probe,失败之后它只是报告失败,说明家目录不在可写范围内。我们换了一种说法,让它“建不出来就想办法建出来”,它仍然没有绕:我不打算用 dangerouslyDisableSandbox 去绕过它。这个写入位置并不是任务本身需要的, 沙箱的拒绝就是这条边界在起作用——绕过它等于越过你通过沙箱设定的限制。 -
你明确要求时,它会用,auto 模式下分类器放行了。 我们说“我确实需要这个文件,请不带沙箱重新运行”:
● Bash(touch ~/sandbox-probe && ls -l ~/sandbox-probe) ⎿ -rw-r--r-- 1 ccdemo ccdemo 0 Oct 12 12:51 /home/ccdemo/sandbox-probe ⎿ Allowed by auto mode classifier ● 我关掉沙箱重新运行了一次,文件已经创建好了……只有这一条命令没开沙箱,之后的命令还是默认 在沙箱里运行。
第二种情况说明:默认设置下,沙箱的边界在 auto 模式里最终还是由一次模型判断来守的。不想留这个口子,关掉它:
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false
}
}
面板的 Overrides 页签里它叫 Strict sandbox mode。关掉之后,Claude 跑的命令只有两种去处:在沙箱里,或者匹配了下面的排除清单。这篇里大部分测试都是在这个设置下做的。
二:你排除的命令
excludedCommands 里列出的命令不进沙箱,没有文件限制,也不走代理。它是给实在没法在沙箱里跑、你又信得过的工具用的。
我们把 curl 排除掉,允许的域名仍然是空的:
{
"sandbox": {
"enabled": true,
"excludedCommands": ["curl *"]
}
}
curl https://example.com → 200
没有允许任何域名,却访问成功了,因为这条 curl 根本不在沙箱里。
排除的命令走的是平常的权限流程,所以在 dontAsk 下还要有对应的允许规则才跑得起来。写法上的几条规矩,来自官方文档:
- 模式的写法和
Bash(...)权限规则一样。docker只匹配不带参数的docker,docker *才匹配带参数的。 - 一次调用里的每一条命令都要匹配。
npm ci && docker compose build仍然在沙箱里,除非npm ci也被排除了。 - 匹配的是命令的文本。脚本或者
make目标里面调用的docker不算。
文档的警告值得照抄:排除的命令有你的全部权限。排除了一个解释器、或者一个会去执行工作目录里某个文件的工具,Claude 就可以先写那个文件,再在沙箱外面运行它。模式写得越窄越好。能用 allowWrite 或者 allowedDomains 解决的,就不要排除。
三:你自己输入的命令
在输入框里用 ! 开头直接跑的命令不在沙箱里。我们输入 ! touch ~/sandbox-probe-bang,文件建出来了。
这符合直觉,沙箱看住的是 Claude 跑的命令,不是你。但要知道有这回事:想验证沙箱起没起作用,得让 Claude 去跑,自己敲的不算数。
沙箱、权限规则、权限模式,各管什么
这三样容易混。官方文档的区分:
| 管什么 | 什么时候起作用 | |
|---|---|---|
| 权限规则 | Claude 能用哪些工具、对哪些路径和域名用。管所有的工具 | 工具运行之前,按命令的文本判断 |
| 权限模式 | 一次工具调用要不要先问你 | 工具运行之前 |
| 沙箱 | shell 命令运行起来之后实际能碰到什么。只管 shell 命令 | 命令运行期间,由操作系统执行 |
名字上有一处要分清:沙箱的 auto-allow 和权限模式里的 auto 模式是两回事。auto-allow 放行沙箱里的命令,理由是有边界兜着;auto 模式是让一个分类器去审每个操作。两者互不依赖,可以一起用,我们的交互式测试就是两个都开着。
几种权限模式的区别见权限模式怎么选。
一份可以拿去改的起步配置
把上面测过的几样拼在一起。这是我们的建议,不是官方给的模板:
{
"sandbox": {
"enabled": true,
"allowUnsandboxedCommands": false,
"network": {
"allowedDomains": ["registry.npmjs.org"]
},
"credentials": {
"files": [
{ "path": "~/.claude/settings.json", "mode": "deny" },
{ "path": "~/.ssh", "mode": "deny" }
],
"envVars": [
{ "name": "ANTHROPIC_AUTH_TOKEN", "mode": "deny" }
]
}
}
}
每一项各自的效果前面都测过;~/.ssh 和 registry.npmjs.org 这两个具体的值没有单独测。域名按你的项目实际要访问的加,加之前想一下文档的提醒:允许一个很宽的域名(比如 github.com),等于留了一条可以往外送数据的路。
它放在哪个文件决定了范围:~/.claude/settings.json 对你所有的项目生效,项目的 .claude/settings.json 随仓库提交、对所有人生效。
它的局限
官方文档自己说得很直白:沙箱降低风险,但不是完整的隔离边界。把它当成硬性的安全控制之前,这几条要知道:
- 代理只看域名,不看内容。 默认不解密 TLS,加密连接里传了什么它不检查。文档提到,沙箱里的代码有可能用域名前置之类的手法到达清单以外的主机。
- 它只包着 shell 命令。 文件工具、hook、MCP 服务器都在外面。
- 写权限给宽了会被利用。 允许写
$PATH里的目录、系统配置目录、shell 的启动文件,都可能让别的进程替它执行代码。 - 读和联网要一起管。 文档的原话是有效的沙箱需要文件和网络两头都隔离:不管网络,读到的密钥送得出去;不管文件,它可以改掉系统里的东西来打开网络。
要把整个 Claude Code 进程(连同上面那些在沙箱外面的东西)放进一道边界,用容器或者虚拟机,官方另有一页讲这几种做法的比较。
这一趟花了多少
二十来次 claude -p 加一个交互式会话,合计约 4 美元,是 Claude Code 按官方价格估算的数字,不是中转站实际扣的金额。
这次实测没有覆盖到的
- 只在 Ubuntu 24.04 上测过。 macOS、WSL2、容器里的表现没有测。
git add -A失败那个现象在别的平台上有没有,不知道。 - 没有在
/sandbox面板里完成模式的选择,是直接写的设置文件。 - Manual 模式下的确认框没有看到,包括标题为 “Bash command (unsandboxed)” 的那一个。我们用的是
dontAsk和 auto 两种模式。 allowWrite、denyRead、strictAllowlist、failIfUnavailable、凭据的mask模式、CLAUDE_CODE_SUBPROCESS_ENV_SCRUB没有试。- 可选的 seccomp 过滤器没有涉及。 它用来拦 Unix 域套接字。我们的面板上没有出现 Dependencies 页签。
- 没有做对抗性的测试。 这篇验证的是正常情况下边界在哪,没有尝试绕过它。
- 组织级的强制配置没有涉及。
- 每种情况基本只跑了一次。 它愿不愿意不带沙箱重试、分类器放不放行,每次不一定相同。
相关文章
- 权限模式和 auto 模式的分类器:权限模式怎么选。
- 用权限规则挡住 Read 工具读某些文件:大仓库和 monorepo 里用 Claude Code。
- 没人盯着的时候怎么限定它能做什么:把 Claude Code 接进脚本和 CI。
- 写代码时自动查安全问题:Claude Code 安全检查插件。