教程Claude Code

Claude Code 沙箱怎么配:让它跑的命令写不出工作目录、连不上没批准的网站

沙箱是操作系统替你看住的一道边界:Claude 跑的命令只能写工作目录,联网要过一个只放行指定域名的代理。本站在 Ubuntu 24.04 上从安装开始实测:Ubuntu 上多出来的一步、默认读得到凭据怎么堵、命令在哪几种情况下会跑到沙箱外面,以及一个 Linux 上的坑:沙箱里 git add -A 会失败。

发布 · 约 17 分钟读完 · 经人工审核()· 本站的内容怎么来的

本站已实测,测试日期:2026-10-12。

本页目录(16 节)
  1. 它管什么,不管什么
  2. 安装(Linux)
  3. 打开沙箱
  4. 确认它真的在起作用
  5. 写:哪里能写
  6. 工作目录里也有写不了的地方
  7. 读:默认几乎都能读
  8. 联网
  9. 凭据:通过中转站用的人要多做一步
  10. 命令什么时候会跑到沙箱外面
  11. 沙箱、权限规则、权限模式,各管什么
  12. 一份可以拿去改的起步配置
  13. 它的局限
  14. 这一趟花了多少
  15. 这次实测没有覆盖到的
  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 页签。
  • 没有做对抗性的测试。 这篇验证的是正常情况下边界在哪,没有尝试绕过它。
  • 组织级的强制配置没有涉及。
  • 每种情况基本只跑了一次。 它愿不愿意不带沙箱重试、分类器放不放行,每次不一定相同。

相关文章

来源

通过 API 中转站使用这些模型?到 Veridrop 免费检测中转站给你的是不是它声称的模型,或者看看中转站排行榜前 100 名。

相关文章