沙箱隔离模型
沙箱是 OpenClaw 默认锁定(default-deny)安全姿态的第一层。是否把一个 session 关进沙箱,由配置项 sandbox.mode 单点驱动;真正落地这个决策的核心源文件是 src/agents/sandbox/runtime-status.ts。
整个沙箱子系统遵循一条朴素原则:先把一切锁死,再按需放宽。系统不会假设某个 session 是可信的;恰恰相反,除非配置明确豁免,否则派生出来的会话默认被隔离。这种姿态把"忘记加限制"这一最常见的人为失误,从"默默放行"变成了"默认拒绝"。
沙箱的实现被拆成三个职责清晰的文件,分别回答三个不同的问题——谁要进沙箱、进沙箱后能做什么、以及沙箱用什么后端实现。runtime-status.ts 负责"是否隔离"的判定,config.ts 负责"如何隔离"的后端与约束,tool-policy.ts 负责"逐工具放行"的细粒度分类。三者解耦,互不越界。
是否隔离
由 shouldSandboxSession 依据 sandbox.mode 决定某个 session 是否进沙箱,并由 resolveSandboxRuntimeStatus 解析出每个 agent 的运行时状态。
如何隔离
定义隔离后端与约束:Docker 容器隔离的挂载/网络限制,或 SSH 远端隔离的目标主机。只管"怎么关",不管"关不关"。
能做什么
在沙箱 session 内对每一次工具调用做分类,由 classifyToolAgainstSandboxToolPolicy 决定放行、降权还是拦截。
谁进沙箱
隔离与否的判定收敛在一个纯函数里。shouldSandboxSession 不做任何 IO,只读三个入参——配置、当前 session key、主 session key——返回一个布尔值。
function shouldSandboxSession(
cfg: SandboxConfig,
sessionKey: string,
mainSessionKey: string,
) {
if (cfg.mode === "off") return false; // 关闭沙箱
if (cfg.mode === "all") return true; // 所有 session 都沙箱
// 默认:main session 不沙箱,其他沙箱
return sessionKey.trim() !== mainSessionKey.trim();
}
export function resolveSandboxRuntimeStatus(params: {
cfg?: OpenClawConfig;
sessionKey?: string;
agentId: string;
}): SandboxRuntimeStatus;
| mode | 行为 | 说明 |
|---|---|---|
off | 不隔离 | 所有 session 直接跑在宿主环境;函数在第一行短路返回 false。 |
| all | 全部隔离 | 每个 session 都进沙箱,包括 main session;第二行短路返回 true。 |
non-main (默认) | 仅非主 session 隔离 | main session 保持原生权限,其余派生 session 进沙箱;走最后一行的 key 比较。 |
off → 永不隔离;all → 永远隔离;默认 non-main 只比较 sessionKey.trim() !== mainSessionKey.trim() —— 主会话原生,派生会话进沙箱。两次 .trim() 是为了让前后空白不会意外把主会话错判成派生会话。逐工具放行策略
一旦某个 session 被判定进沙箱,它发起的每一次工具调用都不再无条件可信。tool-policy.ts 中的 classifyToolAgainstSandboxToolPolicy() 对沙箱 session 内的每次工具调用逐一分类,决定允许 / 降权 / 拦截。
resolveSandboxRuntimeStatus() 从 cfg / sessionKey / agentId 计算出该 agent 的 SandboxRuntimeStatus——它把"该不该沙箱"的判定结果固化成一份逐 agent 的运行时状态,供后续的工具放行逻辑读取。两个函数构成"先定状态、再定每次调用"的两级判定链。
状态解析
读取 cfg、sessionKey、agentId,输出该 agent 的 SandboxRuntimeStatus。是"是否隔离"判定的运行时落地形态,逐 agent 缓存其沙箱状态。
逐工具放行
在沙箱 session 内对每一次工具调用做分类裁决——允许、降权或拦截。细粒度策略发生在这里,而非在"是否进沙箱"那一层。
Docker / SSH 隔离实现
"判定进沙箱"之后,真正把 session 关进隔离环境的是 sandbox/config.ts 配置的隔离后端。OpenClaw 提供两种实现:Docker 容器隔离与 SSH 远端隔离。
Docker 容器隔离
把沙箱 session 跑在一个容器里,挂载与网络都被收紧——只暴露明确允许的目录,网络按需放行。文件系统与宿主隔离,越权写入被容器边界挡在外面。
SSH 远端隔离
把沙箱 session 跑在一台远端主机上,从物理上把执行环境与本地宿主分开。适用于需要把不可信工作负载彻底移出开发者机器的场景。
runtime-status.ts 只决定是否沙箱(WHETHER);config.ts 决定如何沙箱(HOW)——用哪种后端、施加什么约束。两层一旦混在一起,就会出现"判定要隔离却没有可用后端"或"配了后端却忘了启用"的危险缝隙,所以源码刻意把它们分开。失败模式 · 默认锁定
沙箱不是孤立存在的——它与 net-policy 网络层、exec 批准链共同构成默认锁定的纵深防御。每一层的失败模式都被设计成"拒绝"而非"放行"。
SSRF / 私网访问
net-policy 在网络层直接拒绝私网与云元数据 IP,无 fallback;URL 写入日志前先剥离 userinfo 凭证,避免把密钥泄进日志。请求被挡时不会退化成"先试一下"。
exec 批准超时
bash 默认走 allowlist,未命中则进入 ask 批准流程;批准请求一旦超时即失效,agent 必须重试或升级,绝不静默执行。超时的语义是"没批准",而不是"默认通过"。