OpenClaw
← 总览
01
sandbox · isolation model

沙箱隔离模型

沙箱是 OpenClaw 默认锁定(default-deny)安全姿态的第一层。是否把一个 session 关进沙箱,由配置项 sandbox.mode 单点驱动;真正落地这个决策的核心源文件是 src/agents/sandbox/runtime-status.ts。

整个沙箱子系统遵循一条朴素原则:先把一切锁死,再按需放宽。系统不会假设某个 session 是可信的;恰恰相反,除非配置明确豁免,否则派生出来的会话默认被隔离。这种姿态把"忘记加限制"这一最常见的人为失误,从"默默放行"变成了"默认拒绝"。

沙箱的实现被拆成三个职责清晰的文件,分别回答三个不同的问题——谁要进沙箱、进沙箱后能做什么、以及沙箱用什么后端实现。runtime-status.ts 负责"是否隔离"的判定,config.ts 负责"如何隔离"的后端与约束,tool-policy.ts 负责"逐工具放行"的细粒度分类。三者解耦,互不越界。

runtime-status.ts

是否隔离

由 shouldSandboxSession 依据 sandbox.mode 决定某个 session 是否进沙箱,并由 resolveSandboxRuntimeStatus 解析出每个 agent 的运行时状态。

config.ts

如何隔离

定义隔离后端与约束:Docker 容器隔离的挂载/网络限制,或 SSH 远端隔离的目标主机。只管"怎么关",不管"关不关"。

tool-policy.ts

能做什么

在沙箱 session 内对每一次工具调用做分类,由 classifyToolAgainstSandboxToolPolicy 决定放行、降权还是拦截。

02
decision · who gets sandboxed

谁进沙箱

隔离与否的判定收敛在一个纯函数里。shouldSandboxSession 不做任何 IO,只读三个入参——配置、当前 session key、主 session key——返回一个布尔值。

sandbox/runtime-status.ts
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() 是为了让前后空白不会意外把主会话错判成派生会话。
03
policy · per-tool gating

逐工具放行策略

一旦某个 session 被判定进沙箱,它发起的每一次工具调用都不再无条件可信。tool-policy.ts 中的 classifyToolAgainstSandboxToolPolicy() 对沙箱 session 内的每次工具调用逐一分类,决定允许 / 降权 / 拦截。

resolveSandboxRuntimeStatus() 从 cfg / sessionKey / agentId 计算出该 agent 的 SandboxRuntimeStatus——它把"该不该沙箱"的判定结果固化成一份逐 agent 的运行时状态,供后续的工具放行逻辑读取。两个函数构成"先定状态、再定每次调用"的两级判定链。

图 01 · per-tool gating
tool call
沙箱 session 发起
classify
classifyToolAgainstSandboxToolPolicy()
逐工具分类
decide
允许 / 降权 / 拦截
放行裁决
resolveSandboxRuntimeStatus

状态解析

读取 cfg、sessionKey、agentId,输出该 agent 的 SandboxRuntimeStatus。是"是否隔离"判定的运行时落地形态,逐 agent 缓存其沙箱状态。

classifyToolAgainstSandboxToolPolicy

逐工具放行

在沙箱 session 内对每一次工具调用做分类裁决——允许、降权或拦截。细粒度策略发生在这里,而非在"是否进沙箱"那一层。

04
isolation · docker & ssh

Docker / SSH 隔离实现

"判定进沙箱"之后,真正把 session 关进隔离环境的是 sandbox/config.ts 配置的隔离后端。OpenClaw 提供两种实现:Docker 容器隔离与 SSH 远端隔离。

backend · docker

Docker 容器隔离

把沙箱 session 跑在一个容器里,挂载与网络都被收紧——只暴露明确允许的目录,网络按需放行。文件系统与宿主隔离,越权写入被容器边界挡在外面。

backend · ssh

SSH 远端隔离

把沙箱 session 跑在一台远端主机上,从物理上把执行环境与本地宿主分开。适用于需要把不可信工作负载彻底移出开发者机器的场景。

职责边界:runtime-status.ts 只决定是否沙箱(WHETHER);config.ts 决定如何沙箱(HOW)——用哪种后端、施加什么约束。两层一旦混在一起,就会出现"判定要隔离却没有可用后端"或"配了后端却忘了启用"的危险缝隙,所以源码刻意把它们分开。
05
failure modes · default-deny

失败模式 · 默认锁定

沙箱不是孤立存在的——它与 net-policy 网络层、exec 批准链共同构成默认锁定的纵深防御。每一层的失败模式都被设计成"拒绝"而非"放行"。

net-policy

SSRF / 私网访问

net-policy 在网络层直接拒绝私网与云元数据 IP,无 fallback;URL 写入日志前先剥离 userinfo 凭证,避免把密钥泄进日志。请求被挡时不会退化成"先试一下"。

exec approval

exec 批准超时

bash 默认走 allowlist,未命中则进入 ask 批准流程;批准请求一旦超时即失效,agent 必须重试或升级,绝不静默执行。超时的语义是"没批准",而不是"默认通过"。

安全哲学:宁可拒绝也不静默放行——沙箱(隔离决策)+ net-policy(网络层拒绝)+ exec allowlist(执行批准)三层默认锁定,按需放宽。每一层在不确定时都倒向"拒绝",把人为遗漏从"悄悄打开缺口"变成"显式触发拦截"。
Sandbox · 隔离模型
src/agents/sandbox/runtime-status.ts · config.ts · tool-policy.ts
← 返回总览 · github ↗