OpenClaw security audit 实测 32 个配置:10 个报 critical,退出码一次都没变
数据采集时间:2026-10-26 14:00–14:20(Asia/Shanghai)。更新时间:2026-10-26 14:20。 本文只测本机 OpenClaw 2026.10.7 (157d8c2) 的 openclaw security audit。32 个配置场景全部在隔离的 HOME 目录下执行,每个场景只写一份 ~/.openclaw/openclaw.json,然后运行 openclaw security audit --json,记录退出码、summary 与每一条 checkId。命令和原始消息都附在文中,可以逐条复算;没有直接测到的部分标 UNKNOWN。

一、结论先说
32 个场景里,10 个至少报出一条 critical,19 个只报 warn,2 个做到 0 critical / 0 warn。 但 security audit 的退出码在这些场景里一次都没变过:31 个配置合法的场景全部 exit 0,包括那 10 个带 critical 的;唯一一次 exit 1 是配置文件本身无效、审计根本没跑起来。
换句话说,它是一份给人读的体检报告,不是一个能给 CI 当门禁的返回值。想用退出码判断”这台机器安不安全”,&& 后面接部署脚本,等于什么都没拦。
它拦得住的东西很具体:gateway.bind="lan" 却没有 token/password、lan 绑定下 Control UI 没有 origin 白名单、令牌短于阈值、配置文件 0666、state 目录 0777、hooks 令牌太短、exec 被设成 security=full。它放过去的东西同样具体:tools.elevated.enabled=true、browser.noSandbox=true、plugins.load.paths 指向根目录,以及明文的 Telegram botToken。
二、测试口径
- 版本:
openclaw --version输出OpenClaw 2026.10.7 (157d8c2),Nodev24.22.1。 - 隔离:每个场景一个独立
HOME,配置路径经openclaw config file确认落在该HOME的.openclaw/openclaw.json。 - 判定:只认
--json里的summary和findings[].checkId,不按人类可读文案猜。 - 覆盖:18 个网络/权限/钩子/插件场景,7 个文档所列”危险开关”场景,5 个补充对照,2 个干净基线,外加 2 次
--fix实验。 - 复现一个场景只需要三行:
export HOME=$PWD/.openclaw/tmp/audit/home
mkdir -p "$HOME/.openclaw"
printf '%s' '{"gateway":{"bind":"lan","auth":{"mode":"none"}}}' > "$HOME/.openclaw/openclaw.json"
openclaw security audit --json | jq '.summary'
本机真实 ~/.openclaw 不在测试范围内:上面每个 HOME 都是临时目录,测完即弃。
三、整体分布

| 观测项 | 数值 |
|---|---|
| 配置场景 | 32 |
| 配置合法并产出报告 | 31 |
| 配置无效、审计中止 | 1(tools.exec.mode 与 security 同时出现) |
| 含 ≥1 条 critical | 10 |
| 只有 warn | 19 |
| 0 critical / 0 warn | 2 |
出现过的不同 checkId |
21 |
文档目录里收录的 checkId |
131 |
| 退出码非 0 | 1(即那条无效配置) |
出现频次最高的三条:gateway.trusted_proxies_missing(24/32)、gateway.http.no_auth(5/32)、gateway.control_ui.allowed_origins_required(5/32)。summary.attack_surface 是每次都会带的 info,不算问题。
四、它拦得住什么
挑几组最能说明”严重度分级不是装饰”的结果:
| 场景 | 结果 |
|---|---|
| 空 HOME,连配置文件都没有 | 1 critical + 2 warn:gateway.loopback_no_auth、gateway.trusted_proxies_missing、gateway.http.no_auth |
bind=lan + auth.mode=none |
3 critical:gateway.bind_no_auth、gateway.control_ui.allowed_origins_required、gateway.http.no_auth;另 1 warn gateway.auth_no_rate_limit |
bind=lan + token(32 字符) |
仍 1 critical:gateway.control_ui.allowed_origins_required |
bind=lan + password(23 字符) |
1 critical(同上)+ 3 warn:gateway.password_too_short、gateway.auth_no_rate_limit、config.secrets.gateway_password_in_config |
| 配置 0666 | 1 critical:fs.config.perms_writable(原文写明”其他用户可改 gateway/auth/tool 策略”) |
| state 目录 0777 | 1 critical:fs.state_dir.perms_world_writable |
| 配置文件 0644 且含明文 botToken | 1 critical:fs.config.perms_world_readable |
两个细节值得记下来。
第一,空配置就有 1 条 critical。gateway.loopback_no_auth 的判定条件是”绑定 loopback 且没配 auth secret”,没有配置文件时同样成立。刚装完还没跑 openclaw setup 的机器,第一次体检不会是绿的。
第二,加了 token 不等于 lan 绑定就安全。31 个场景里 gateway.control_ui.allowed_origins_required 出现了 5 次,其中 2 次是在 token / password 都配好的情况下——审计认为非 loopback 绑定下 Control UI 缺少浏览器来源白名单本身就是 critical,和”有没有密码”是两件事。
想真正做到 0 critical / 0 warn,我试了两组:loopback + token + 非空 trustedProxies,以及 lan + token + rateLimit + publicOrigin + controlUi.allowedOrigins。两组都是 0/0,只留一条 info。所以”干净”是可达的,只是要多配 2 到 5 个键。
五、它放过了什么
这部分比第四节更重要,因为它决定了你不能把 security audit 当成唯一的安全检查。
1)提权工具开关不报。 tools.elevated.enabled=true,结论是 0 critical、1 warn(gateway.trusted_proxies_missing,与开关无关)。更微妙的是 summary.attack_surface 的正文:默认配置和打开提权后的正文逐字相同,都写着 tools.elevated: enabled。也就是说,这条 info 不能用来判断提权是不是被显式打开过。
2)浏览器不沙箱不报。 browser.noSandbox=true,同样只有那条 trusted_proxies_missing。配置 schema 里这个字段的说明写着”尽可能保持关闭,因为进程隔离保护会被削弱”——schema 有警告,审计没有对应 finding。
3)插件扫描路径给到根目录不报。 plugins.load.paths=["/"] 的结果和基线完全一样。schema 里这个字段的说明提醒”避免宽泛路径夹带无关可执行内容”,审计侧没有检查。
4)明文渠道令牌不按”密钥落盘”报。 把 Telegram botToken 明文写进配置、文件权限 0600,审计给的是 0 critical、1 warn(还是那条 trusted_proxies),没有任何 config.secrets.* 相关 finding。同一份配置改成 0644,才多出一条 fs.config.perms_world_readable critical。作为对照:gateway.auth.password 明文落盘会报 config.secrets.gateway_password_in_config(warn),hooks.token 明文落盘会报 config.secrets.hooks_token_in_config(info)。三个都是长期凭据,只有两个进了”密钥落盘”检查。
5)hooks 打开但完全没配 token,也不报”缺令牌”。 hooks.enabled=true 且不给 token,得到 2 条 warn:hooks.default_session_key_unset、hooks.allowed_agent_ids_unrestricted。反而是给一个 3 字符的短 token 时会多一条 hooks.token_too_short——配了烂令牌比压根不配令牌多一条警告。这条 ingress 到底怎么鉴权,本文没有实测(UNKNOWN),只记录审计层面的表现。
六、退出码:0 不代表安全
这是最容易被自动化踩到的一点。10 个带 critical 的场景,退出码全是 0;--deep 在没有运行中 Gateway 时也返回 0,只多一条 warn gateway.probe_failed。
唯一一次 exit 1 是配置读取阶段就失败:
# tools.exec.mode 与 tools.exec.security 同时出现
openclaw security audit --json # exit 1
原文消息是 tools.exec.mode: mode cannot be combined with security or ask in the same exec object。顺带说明:这一条 openclaw config validate --json 也会判 valid: false、退出码 1,两个命令在这件事上一致。
要给流水线加门禁,应该解析 JSON 而不是看退出码,例如:
openclaw security audit --json | jq -e '.summary.critical == 0'
七、–fix 与 doctor –fix 的边界

--fix 只修权限这类确定性动作。 对一个 0666 的配置跑 openclaw security audit --fix:文件权限从 0666 变成 0600,再跑一次审计,fs.config.perms_writable 消失,剩 1 warn;修复输出里 configWritten: false,changes: [],actions 只有一条 chmod。
对 bind=loopback + auth.mode=none 跑同样的 --fix:配置文件一个字节都没变,critical 原样留着。这和文档一致——--fix 明确”不轮换 token/password、不改 gateway bind/auth/network”。
doctor --fix 会重写整份配置。 把已退休的 gateway.controlUi.dangerouslyDisableDeviceAuth 写进配置后运行 openclaw doctor --fix,这个键确实被删掉了(controlUi 变成 {}),但文件从 157 字节涨到 2,468 字节:多出 agents.entries.main、meta.migrations、wizard 记录,以及 34 个 skills.entries 的 enabled: false 默认项。文档说这个键”retired, inert”,说 doctor 会移除它,两点都成立;但”移除一个键”的实际副作用是把配置文件按当前版本模板补齐。在真实机器上跑之前,先备份。
八、文档对账:131 个 checkId,6 个”今天会被跟踪”的开关
文档 Security audit checks 页共列出 131 个 checkId(含 browser.* 这类通配条目)。本次 32 个场景只触发了 21 个,其余没触发不代表不存在,只代表我的配置没碰到——这一点必须说清楚。
Network exposure 页有一个折叠块,标题是”Flags tracked by the audit today”,列了 6 项。逐项测:
| 文档所列开关 | 是否产生 finding |
|---|---|
gateway.controlUi.dangerouslyAllowHostHeaderOriginFallback=true |
是:config.insecure_or_dangerous_flags + 专属 gateway.control_ui.host_header_origin_fallback |
security.audit.suppressions configured |
是:config.insecure_or_dangerous_flags + security.audit.suppressions.active |
hooks.gmail.allowUnsafeExternalContent=true |
是 |
hooks.mappings[].allowUnsafeExternalContent=true |
是(原文写作 hooks.mappings[0].allowUnsafeExternalContent=true) |
tools.exec.applyPatch.workspaceOnly=false |
是 |
plugins.entries.acpx.config.permissionMode=approve-all |
否:本机未安装 acpx 插件,配置合法但审计无任何 finding |
另外测了文档在”所有 dangerous\*/dangerously\* 键”清单里列出、但没有写进那 6 项的一个键:browser.ssrfPolicy.dangerouslyAllowPrivateNetwork=true。结果是会产生 config.insecure_or_dangerous_flags finding。所以那 6 项清单至少漏了一项;反过来,acpx 那一项在插件未安装时不生效,审计没法替你做”插件级权限”判断。
九、给你的三条实用结论
- 别用退出码做门禁,用
jq -e '.summary.critical == 0',否则 10 次 critical 全会静默通过。 - session 里默认就有
security.audit.suppressions.active这条 info:一旦你为了”让报告变绿”加了 suppression,输出里会一直留痕,这是好事——说明有人主动接受过风险,而不是风险消失。 - 体检报告要和 schema 说明一起读。
tools.elevated、browser.noSandbox、plugins.load.paths三项,schema 里都写着”谨慎开启”,审计却一条都不报。这三项属于”配置设计层面的取舍”,只能靠人读。
十、数据与来源
- 版本与命令:本机
openclaw --version=OpenClaw 2026.10.7 (157d8c2),Nodev24.22.1,采集时间 2026-10-26 14:00–14:20 +08:00。 - 文档:
https://docs.openclaw.ai/cli/security、https://docs.openclaw.ai/gateway/security/audit-checks、https://docs.openclaw.ai/gateway/security/network-exposure,同一时段抓取;131 个checkId是从audit-checks表格里数出来的。 - 截图:三张都是上述文档页在 Chrome 无头模式、1440×950 视口下的实际渲染,未做裁剪以外的修改。
- 未测:真实 Gateway 的
--deep联网探针、插件代码扫描、真实渠道令牌的鉴权行为。这些标 UNKNOWN,本文不做推断。








