OpenClaw 的密钥与凭据怎么存:SecretRef 四种来源、审计迁移与哨兵机制拆解

2026-08-17

给 OpenClaw 配模型供应商时,最省事的做法是把 API Key 直接写进 openclaw.json。跑通了,能用。问题出在下一步:这个网关上跑的是一个有 shell 工具、能读文件的 Agent,它读得到配置文件、.env,还有生成出来的 agents/*/agent/models.json。API 层面的脱敏在这里一点用没有——不是接口把值吐出来的,是 Agent 自己 cat 出来的。

官方文档对这件事的说法很直接:明文凭据只要躺在 Agent 能读的路径里,就仍然是 Agent 可读的,包括 openclaw.json.env、退役的 auth-profile JSON 归档,以及生成的 models.json。SecretRef 就是用来缩小这个本地爆炸半径的机制,但它是按凭据逐项 opt-in 的:明文写法没有被废弃,你不配,一切照旧。

所以这篇讲三件事:SecretRef 到底是个什么形状的东西、迁移要做到什么程度才算做完、这套机制明确管不到哪些地方。

SecretRef 的四种来源和它们各自的校验规则

全系统只有一种对象形状:

{ source: "env" | "file" | "exec" | "store", provider: "default", id: "..." }

provider 是你在 secrets.providers 里定义的别名,id 的语法则随 source 变化。这块的校验是硬的,写错不会”尽力而为”,会直接失败:

sourceid 语法关键约束
env^[A-Z][A-Z0-9_]{0,127}$支持 "${OPENAI_API_KEY}" / "$OPENAI_API_KEY" 简写;值缺失或为空即解析失败;可用 allowlist 做精确名单
file绝对 JSON 指针 /...,或 singleValue 下的字面量 value路径要过属主/权限检查;timeoutMs 默认 5000、maxBytes 默认 1 MiB;RFC 6901 转义 ~~0/~1
exec^[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$,支持 secret#json_key 选择器id 里不许有 ... 作为路径段;command 必须是绝对路径的普通文件,不能是软链接
store同 env 的大写命名法读共享状态 SQLite;本版本只解析 Gateway 级 team 作用域,identity 作用域官方标注为计划中,目前尚未提供

provider 别名统一要匹配 ^[a-z][a-z0-9_-]{0,63}$,且是按 source 区分的:非默认别名,以及 file / exec 两类 provider,必须能解析到 source 匹配的显式条目。定义放在 secrets.providers 下,secrets.defaults 给四种 source 各指一个默认别名:

{
  secrets: {
    providers: {
      default: { source: "env" },
      teamstore: { source: "store" },
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json", // or "singleValue"
      },
      vault: {
        source: "exec",
        command: "/usr/local/bin/openclaw-vault-resolver",
        args: ["--profile", "prod"],
        passEnv: ["PATH", "VAULT_ADDR"],
        jsonOnly: true,
      },
    },
    defaults: {
      env: "default",
      file: "filemain",
      exec: "vault",
      store: "teamstore",
    },
  },
}

有一个坑文档专门点了名:不要在配置的 env 块里写 file:... 字符串。那个块是字面量、不做覆盖解析,永远不会被解析成文件内容。要用文件里的密钥,就在受支持的凭据字段上挂 file 型 SecretRef,mode: "singleValue"id 固定写 "value"。Windows 上另有一条 fail-closed 规则:fileexec provider 无法验证路径 ACL 时解析直接失败,没有绕过开关。

迁移做到什么程度才算做完

这是最容易自我感觉良好的一步。把 ref 配上去、服务起来了,不等于迁移完成——旧的明文残留还在盘上。文档给的操作流是三步:

openclaw secrets audit --check
openclaw secrets configure --apply
openclaw secrets audit --check

重点在第三步:复审不干净就不算做完。审计查这几类问题:openclaw.json / SQLite auth-profile 行 / .env / agents/*/agent/models.json 里的静态明文;生成的 models.json 里的敏感 provider 头部残留(按 authorizationx-api-keytokensecretpasswordcredential 这类名称启发式识别);未解析的 ref;优先级遮蔽(REF_SHADOWED);以及 store 残留(STORE_PLAINTEXT_RESIDUE)。

exec 类有个默认行为要记住:审计默认跳过 exec SecretRef 的可解析性检查,避免命令产生副作用;要真跑一遍解析器得加 --allow-execconfigure 的预检、apply 的 dry-run 同理;写入模式下计划里含 exec ref 或 provider 而不加这个开关,会被直接拒绝。

按文档的说法,production 要同时满足四条才叫迁移完成:受支持的凭据都换成 SecretRef;上述四个位置的历史明文都被清理(退役的 auth JSON 是 doctor 的迁移输入,secrets apply 不改写它,要用 openclaw doctor --fix);audit --check 干净;剩下不受支持或会轮换的凭据靠系统隔离、容器隔离或外部凭据代理另行保护。

还有一条容易踩的:这套流程故意不写含历史明文的回滚备份。安全模型是预检先过、运行时激活先验证再提交、写文件用原子替换。想着”改坏了回滚”的操作习惯在这里不成立。

哨兵:模型凭据在进程里长什么样

对于 SecretRef 支撑的模型供应商凭据,OpenClaw 会在模型鉴权解析阶段铸一个进程内的不透明哨兵值,形如 oc-sent-v2.<authenticated-ciphertext>.end。auth 存储、stream options、SDK 配置、日志、错误对象和大部分运行时自省看到的都是它,不是真凭据;真值在请求离开进程前的最后一刻,由受保护的 fetch 在 URL 和 header 里替换回去。

注入点按 SDK 能力分两档:支持自定义 fetch 的 SDK 拿到受保护 fetch,SDK 内部一直持有哨兵;不支持的在构造 client 之前才解包。形状像哨兵但认不出来的值会在发起网络请求前 fail closed——OpenClaw 宁可拒发,也不把未解析的哨兵转发给供应商。

要明确的是:哨兵不是进程隔离。真值仍然在同一进程的内存里,并会在最终的适配器边界出现;没走 SecretRef 的普通环境变量凭据仍是明文,压根不在这套机制里。事故响应或兼容性排查时可以用 OPENCLAW_SECRET_SENTINELS=off 关掉哨兵铸造,但它不会关掉精确值脱敏注册。

共享密钥库:secret 和 env 两种 kind 的差别在哪

共享密钥库是 Gateway 级、team 作用域的一块地方,供所有使用同一状态数据库的 Gateway 进程取值,在 Control UI 的 Settings → Secrets 或本地 openclaw secrets store 管理(CLI 只操作本地状态库,不接受 Gateway URL 或 token)。条目分两种 kind,它控制的是 CLI 披露行为,不是 SecretRef 解析

  • secret:保存后只写不读。Gateway list 结果、Control UI、CLI 的 list/get 都不含其值,也没有 reveal RPC。
  • env:管理员在 Control UI 里可见,store list / store get 也会返回;还会被加进 OpenClaw exec 工具所执行命令的环境里,位置在继承的进程值之后、显式 per-call env 之前。

env 的注入覆盖直接工具调用、Code Mode(其 guest 通过同一个 openclaw:core:exec 工具进 shell)、沙箱内 exec 和 node 托管的 exec,但不覆盖 provider 原生 harness 里执行的命令——Codex 的 app-server 及其沙箱 exec-server、ACP 子进程都自己组装子进程环境,不经过 OpenClaw 的 exec 准备。另外 store 快照每次 Agent run 只读一次,run 中途新增的条目下一次 run 才生效。

命名沿用大写语法,每个 UTF-8 值上限 64 KiB,够放 PEM 私钥和 service account JSON。secret 条目必须有值,空的会被拒——它只会在下游表现为一个莫名其妙的鉴权失败。

一条必须知道的事实:store 里的值在静态存储时不加密,明文躺在共享状态 SQLite(state/openclaw.sqlite)里,靠和其它凭据一样的 0600 文件、0700 目录权限保护。需要更强存储隔离的,文档指向外部 exec provider,比如 1Password 插件或 Vault。另外用 CLI 改了被引用的值之后要跑 openclaw secrets reload——Control UI 的增删改会自动刷新活跃快照,CLI 的离线写入不会。

出口代理:让子进程用密钥但拿不到明文

默认关闭的 secret egress proxy 解决另一个场景:Gateway 托管的 Agent 子进程要用共享库里的 secret,但你不想把明文塞进它的环境变量。开启后子进程环境里放哨兵,一个 Gateway 自有的 loopback 代理在出口前把它替换进请求 URL、header 和流式 body。

每个 secret 还必须指定允许替换的 HTTPS 主机,主机名以小写 ASCII/punycode 存储、精确匹配,不支持通配、后缀和端口。没绑主机的 secret 永远不会被替换:

openclaw secrets store set OPENAI_API_KEY --allow-host api.openai.com
openclaw config set secrets.egressProxy.enabled true --strict-json
openclaw gateway restart

被拒绝的请求会把 secret 名字和该目标所需的 store set ... --allow-host ... 命令一起打出来,不用猜。代理认证走 Basic proxy auth,用户名 openclaw,密码每次 run 随机生成、run 结束即失效;CA 每次 Gateway 启动生成,关闭时删除,从不装进系统信任库。

限制清单值得贴出来对照,因为它决定了这东西能不能用在你的场景:

限制项具体行为
HTTP/2上游不支持,代理对上游用 HTTP/1.1
WebSocket不做改写
非 443 端口 HTTPS不是支持的兼容目标
作用域只有 team store 参与,identity 作用域密钥不支持
主机策略仅精确主机名授权,不校验解析出的 IP,也拦不住被允许的源反射凭据
明文 HTTP直接拒绝,不升级也不替换
适用范围只作用于 Gateway 托管的 exec;沙箱、远程 node exec、provider 原生 harness 都不走这个代理
后台子进程Agent run 结束即失去代理授权

bypassHosts 用来放必须端到端 TLS 的证书固定客户端,走盲 CONNECT 隧道,隧道内不做替换。

1Password:插件路径和 MCP 路径不是一回事

文档列了 1Password 与 OpenClaw 的四种独立配合方式,其中真正用于”网关自己在启动/重载时解析引用”的只有插件那条。

插件路径要先在 Gateway 主机上装好 1Password CLI(op),准备一个服务账号,然后:

openclaw plugins enable onepassword
mkdir -p ~/.openclaw/credentials/onepassword
chmod 700 ~/.openclaw/credentials/onepassword
printf '%s' "$OP_SERVICE_ACCOUNT_TOKEN" > \
  ~/.openclaw/credentials/onepassword/service-account-token
chmod 600 ~/.openclaw/credentials/onepassword/service-account-token
unset OP_SERVICE_ACCOUNT_TOKEN

设了 OPENCLAW_STATE_DIR 就用那个目录,不用 ~/.openclaw。然后生成并应用 SecretRef 计划:

openclaw onepassword secretref setup \
  --openai-id op://Automation/OpenAI/credential \
  --plan-out ./openclaw-1password-secrets-plan.json

openclaw onepassword secretref status
openclaw secrets apply --from ./openclaw-1password-secrets-plan.json --dry-run --allow-exec
openclaw secrets apply --from ./openclaw-1password-secrets-plan.json --allow-exec
openclaw secrets audit --check --allow-exec
openclaw secrets reload

setup 至少要给一个目标。计划应用前,status 可能一边报 provider 未配置、一边报 prerequisites ready: yes,这是正常的;应用后 ready: yes 才表示三者都就位。

插件接受原生的 op://<vault>/<item>/<field>op://<vault>/<item>/<section>/<field> 引用,只解析已注册的 OpenClaw 凭据目标。安全侧的具体数字:单次请求上限 32 个引用,读取四个并发、每读七秒超时,provider 级 90 秒超时;传 token 前会解析 op 可执行文件,拒绝可被另一个本地账户写入或 ACL 无法验证的路径。它还强制 OP_LOAD_DESKTOP_APP_SETTINGS=falseOP_BIOMETRIC_UNLOCK_ENABLED=false,避免无人值守读取触发桌面审批弹窗。

官方 1Password MCP server 是另一条路:面向 1Password Environments 的桌面工作流,处于 beta,需要桌面应用并对每次交互显式批准,密钥值不返回给 MCP 客户端或模型。但它不提供对任意保险库条目的无人值守服务账号访问,OpenClaw 插件也不调用它。

另外还有 bundled 的 1password skill,教 Agent 优先用 op runop inject 而不是把密钥值写盘;已经接到 SecretRef 目标上的凭据由 OpenClaw 工作流自己解析,Agent 不需要调 op

排查方面,op 缺失就装 CLI 或用 CLAW_1PASSWORD_OP 指绝对路径;op 不受信任就换成当前用户或 root 属主的文件,并去掉它及父目录链的 group/other 写权限;鉴权失败用 openclaw onepassword status 查 token 文件与服务账号的保险库权限。

密钥解析失败时网关会怎么办

这块语义比想象中细,它决定了一次密钥后端抖动会不会让整个网关起不来。

密钥解析成的是一份内存中的运行时快照,在激活阶段一次性解析完,不是在请求路径上懒加载。冷启动时,如果一个可重试的失败发生在”已映射且支持隔离”的非 Gateway 属主上,Gateway 会照常启动,把该属主记为 configured-unavailable,并发一条脱敏的降级告警。已映射的属主包括模型供应商与 skills、媒体/TTS/cron provider、符合条件的 auth profile、按 Agent 的记忆、沙箱 SSH、渠道账号和 manifest 声明的插件路由。反过来,Gateway ingress 鉴权、结构非法的 ref 或解析值、fail-closed 属主、运行时属主未被映射的 ref,仍会让启动直接失败。

重载则是每个已映射属主独立校验、再原子发布一份快照:健康属主刷新;符合条件的失败属主保留 last-known-good 值进入 stale,前提是它的 ref 标识、provider 定义和完整的非密钥属主契约都没变;变更过或新出现的失败属主进入 cold。严格失败会拒绝整次重载,保留当前活跃快照。

对应的日志与事件码:SECRETS_RELOADER_DEGRADED / SECRETS_RELOADER_RECOVERED 是一次性系统事件;每个受影响属主发 SECRETS_DEGRADED,provider 级别的整体故障则只发一条 SECRETS_PROVIDER_DEGRADED,带完整受影响属主列表。这些告警含脱敏原因、coldstale 状态和重试提示,不含解析出的值,也不含 SecretRef idopenclaw doctor 会列出 cold 和 stale 属主及其受影响的配置路径,想把它的输出读顺,可以对着 doctor 体检输出怎么读 一起看。

两条会咬人的规则:模型供应商属主在显式 ref 失败后不回退到环境变量或 auth-profile 凭据;明文与 ref 并存时 ref 优先,并发 SECRETS_REF_OVERRIDES_PLAINTEXT

另外,只有”实际生效的表面”才会校验 ref。禁用的渠道账号、没被任何启用账号继承的顶层渠道凭据、禁用的工具或功能,未解析的 ref 不阻塞启动,只发一条非致命的 SECRETS_REF_IGNORED_INACTIVE_SURFACEgateway.remote.token / gateway.remote.password 的激活条件跟你走哪条远程方案直接相关:gateway.mode=remote、配了 gateway.remote.url、或 gateway.tailscale.modeserve/funnel 都算激活,具体见 远程访问三条路。这几个字段挂 SecretRef 时,启动与重载会以 SECRETS_GATEWAY_AUTH_SURFACE 码记录它是 active 还是 inactive,并附上判定依据。

什么时候这套机制不适用,以及它没解决什么

它不是进程隔离边界。 这句话文档反复说了三次,值得当成使用前提。SecretRef 阻止的是凭据被持久化到配置和生成的模型文件里,哨兵阻止的是凭据出现在日志、SDK 配置和大部分自省接口里,但真值始终在同一进程内存中。威胁模型里包含”Agent 能执行任意代码读同进程内存”的,得靠操作系统隔离、容器隔离或外部凭据代理。

有几类凭据被有意排除在外。 运行时铸造的凭据、会轮换的凭据、OAuth refresh 材料,都不在只读 SecretRef 解析的范围内。共享 store 也不加密静态存储,把它当成”比写在 openclaw.json 里好一点”是准确的,当成”金库”就不准确。

出口代理覆盖不到沙箱和远程 node exec。 如果 Agent 主要在沙箱里干活,共享库的 secret 条目在那边根本不可用。沙箱、工具策略与提权三者的边界另有一套规则,见 沙箱、工具策略与提权的边界

备份和副本是最容易漏的一块。 备份文件、复制出去的配置、旧的生成模型目录,只要没删除、没移出 Agent 信任边界、没单独隔离,就仍然是生产密钥——审计干净指的是当前那几个路径干净。

最后一点操作建议:走 exec provider(Vault、Bitwarden bwspass、sops、1Password 都属于这一类)时,把 --allow-exec 当成默认习惯加在 audit、dry-run 和 apply 上,否则你验的是一个绕过了真实解析器的假绿。这类解析器往往由插件提供,插件被禁用、移除、失信或不再声明该 integration 时,挂在它上面的活跃 SecretRef 会 fail closed——插件的启用状态因此成了密钥链路的一部分,排查时容易忽略,装载与信任模型可参考 插件体系怎么装怎么写

延伸阅读


本文依据 OpenClaw 官方仓库(github.com/openclaw/openclawdocs/ 下的官方文档整理,核对日 2026-08-17。 我们没有安装或运行过 OpenClaw,因此不涉及界面外观、操作手感与实测耗时的任何描述; 文中的默认值、命令与配置项均为文档口径,不构成对实际运行结果的保证。 该项目迭代很快,请以仓库最新内容为准。接入即时通讯平台前,请自行确认所在平台的规则与合规要求。

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。