沙箱与运行时
第 4 周开篇。前三周学的是"助理怎么想、怎么说、怎么扩本领";从今天起换个视角:助理会真的去执行命令、读写文件——怎么保证它(或被注入的恶意指令)不搞坏你的机器?答案是 Docker 沙箱。今天讲沙箱怎么把执行关进隔离容器,明天(Day 17)讲更完整的安全信任模型。代码在 src/agents/sandbox/(~40 文件),底层调 docker CLI。
rm -rf ~"。如果命令直接在你的真实电脑上跑——你的家目录就没了。AI 会犯错、会被骗,这不是"可能"而是"迟早"。所以不能让它的手直接伸到你的真实系统里。今天的沙箱就是那副"防护手套"。network:none 断网)、地面焊死不能改(readOnlyRoot 根只读)、进去的人交出一切权限徽章(capDrop:ALL)——要用什么,出门前单独申请(按需放开)。舱门有门禁校验(validateSandboxSecurity),死活不让你把"通往主控室的钥匙"(docker.sock)带进舱里。实验做完,样本通过传递窗递进递出(docker exec 注命令)。记住这座实验室,今天全通。为什么要沙箱
助理的工具能跑 shell 命令、改文件。如果模型被提示注入攻击、或自己犯错跑了 rm -rf,直接在你的真实系统上执行就是灾难。沙箱把工具执行关进一个隔离的 Docker 容器:容器里怎么折腾都伤不到宿主。
rm -rf /,在沙箱里 vs 在宿主上
无沙箱(mode=off,直接跑宿主):rm -rf / → 真删你的系统文件,灾难。有沙箱(mode=all):命令被
docker exec 送进隔离容器执行 → 它只能删容器里的东西,而容器根目录还是 readOnlyRoot:true(只读)→ 大部分直接失败;就算删掉了 /tmp 这类可写目录,那也是内存盘(tmpfs),容器一销毁就没了,你的真实机器毫发无伤。→ 这就是"隔离舱"的意义:里面随便炸,外面安全。三份镜像
| Dockerfile | 基于 | 内容/用途 |
|---|---|---|
Dockerfile.sandbox | debian:bookworm-slim | 基础:bash/curl/git/jq/python3/ripgrep,非登录用户 sandbox,CMD sleep infinity |
Dockerfile.sandbox-common | 基础镜像 + | 语言工具链:nodejs/python3/golang/rustc/cargo,可选 pnpm/bun/brew |
Dockerfile.sandbox-browser | 基础镜像 + | chromium/xvfb/x11vnc/novnc,EXPOSE 9222(CDP)/5900(VNC)/6080(noVNC) |
CMD sleep infinity 很关键——容器不是"跑一个命令就退出",而是常驻空转,命令通过 docker exec 注入进去执行(L07)。这样一个容器可复用多次执行,不用每条命令都起新容器(快)。浏览器沙箱额外提供 CDP/VNC,让助理能操控一个隔离的浏览器。默认极度收紧
resolveSandboxDockerConfig(config.ts:76-120)的安全默认:
readOnlyRoot: true // 根文件系统只读
tmpfs: ["/tmp","/var/tmp","/run"] // 只有这几个临时目录可写(内存盘)
network: "none" // 默认断网!
capDrop: ["ALL"] // 丢弃所有 Linux capability
env: { LANG: "C.UTF-8" } // 环境变量默认几乎为空
// mode 默认 "off"(config.ts:191);workspaceAccess 默认 "none"
dangerouslyAllow* break-glass 开关(config.ts:27-31)——名字带 dangerously,逼你意识到风险才敢开。👶 小白:既然沙箱这么安全,为什么 mode 默认还是 off(不沙箱)?不是自相矛盾吗?
👨🏫 老师:不矛盾,看信任模型(明天 Day 17 详讲)。OpenClaw 定位是"你在自己电脑上、给自己用"——它信任"你",你本来就有权在自己机器上跑命令,硬套沙箱反而处处受限、体验差。沙箱真正要防的是"不受信任的来源":外部注入、派生的子任务。所以有 non-main 模式——你亲自对话的主会话不沙箱(够信任),助理自动派生的会话就沙箱化。安全不是"处处堆最重的防护",而是"按信任级别分配防护"。
要不要沙箱化
shouldSandboxSession(runtime-status.ts:10-18)按 mode 决定:
// mode = "off" → 不沙箱(默认,直接在宿主执行)
// mode = "all" → 全部沙箱
// mode = "non-main" → 只要不是"主 session"就沙箱化
non-main 模式很实用:你自己直接对话的主会话可能需要完整能力(不沙箱),但助理派生的子任务/来自外部的会话就沙箱化——按"信任级别"区别对待。被工具策略拦截时会给人类可读的诊断(提示改哪个配置、跑 openclaw sandbox explain)。默认 off 是因为个人自部署场景信任度高(Day 17 信任模型)。容器生命周期
核心 src/agents/sandbox/docker.ts(567 行):
execDockerRaw(:67):spawn("docker", args)封装,找不到 docker 抛友好错误。buildSandboxCreateArgs(:317):拼docker create参数——先validateSandboxSecurity(L06),再加--read-only/--tmpfs/--network/--cap-drop/强制--security-opt no-new-privileges/资源限制(pids/memory/cpus)。ensureSandboxContainer(:492):幂等保证容器存在,用computeSandboxConfigHash检测配置变化 → 不匹配则重建。
no-new-privileges 强制加上:禁止容器内进程提权(比如通过 setuid 程序变 root)。配置哈希让"改了沙箱配置自动重建容器",避免旧配置残留。容器记入 registry ~/.openclaw/sandbox/containers.json,定期 prune 清理过期的。防配置注入
创建容器前 validateSandboxSecurity(validate-sandbox-security.ts,344 行)做校验,防"配置注入":
BLOCKED_HOST_PATHS(:18-33):禁止把/etc /proc /sys /dev /root及各种docker.sock挂进容器。validateBindMounts(:234):拒绝非绝对路径、覆盖/、目标是保留路径;含软链接逃逸加固(解析真实路径再查)。validateNetworkMode:禁network=host/container:*。- 禁 seccomp/apparmor
unconfined。
/var/run/docker.sock 是 Docker 守护进程的控制接口。如果把它挂进沙箱容器,容器里就能命令 Docker 起一个新的特权容器、挂载宿主根目录——等于直接逃出沙箱、拿下整台宿主机。这是容器逃逸的经典手法。所以 OpenClaw 明确把它列入禁止挂载清单。软链接逃逸加固也是同理:防止用 /safe/link → /etc 这种软链接绕过路径检查。这些校验体现了"沙箱设计者要主动想清楚所有逃逸路径并堵死"。exec 进沙箱执行
// src/agents/bash-tools.shared.ts:49 buildDockerExecArgs
// docker exec -i [-t] -w <workdir> -e K=V … <container> /bin/sh -lc "<cmd>"
// 特意跳过宿主 PATH(防 Windows PATH 污染),用 OPENCLAW_PREPEND_PATH 前置
// 环境变量先经 sanitizeEnvVars 过滤(下条 Day 17 讲)
// exec host 路由 src/infra/exec-approvals.ts:10
type ExecHost = "sandbox" | "gateway" | "node";
// 默认偏好 sandbox;但若该 session 未激活沙箱,回落到 gateway 宿主执行(host-first)
docker exec 注入到常驻容器里跑(L02)。文件操作走 fs-bridge(fs-bridge.ts)同样用 docker exec。三种 exec host(sandbox/gateway/node):优先沙箱,没沙箱就宿主(gateway),或推给连接的节点设备(node,Day 18)。宿主执行前还有 sanitizeHostBaseEnv/validateHostEnv 拦危险变量。今日小结 + 动手
🧠 今天你应该能回答
- 为什么会自主执行代码的助理必须有沙箱?
- 三份镜像各是什么?CMD sleep infinity 为什么这么设?
- 默认收紧配置有哪些?"deny by default" 好在哪?
- 三种沙箱 mode 的区别?
- 为什么禁止挂载 docker.sock?exec 怎么进沙箱执行?
✋ 动手
cd /Users/bitmart/work/codes/github/openclaw
cat Dockerfile.sandbox
sed -n '76,120p' src/agents/sandbox/config.ts # 默认收紧
sed -n '18,33p' src/agents/sandbox/validate-sandbox-security.ts # 禁挂路径