ARTICLE · 1200655
OpenClaw 密钥怎么管才安全?官方 SecretRef 实践记录
一篇有官方文档背书的密钥管理实践:不臆想、不泄露、可复现。 后记:我最终将模型的密钥转移了,没迁移gateway token,担心移走之后gateway没办法启动登录进来。
引子:一次"被锁在门外"的教训
前两天我在配置 OpenClaw 的时候,踩了一个挺典型的坑。
为了"更安全"一点,我把登录用的 token 从明文改成了环境变量引用 ${env:GATEWAY_AUTH_TOKEN},还老老实实写进了 ~/.openclaw/.env。结果一重启 Gateway,页面直接登不上去了。
排查半天才搞明白两件事:
第一,Gateway 服务根本不读普通 .env;
第二,登录 token 属于"fail-closed"(解析失败就拒绝)——解析不出来,等于自己把自己锁在门外。
这个坑逼着我认真读了一遍 OpenClaw 的官方文档。今天就把结论完整讲清楚:密钥到底该放哪、怎么被读取、官方推荐怎么做。所有做法都能在 OpenClaw 官方文档 里找到出处。
一、官方立场:明文能用,但推荐 SecretRef
先给一个可能反直觉的结论。官方文档写得很清楚:
"OpenClaw supports additive SecretRefs so supported credentials do not need to live as plaintext in configuration."—— Secrets management · OpenClaw Docs
翻译过来:OpenClaw 支持 SecretRef,让受支持的凭据不必以明文形式出现在配置里。但文档紧接着用一句话定了调子:
"Plaintext still works. SecretRefs are opt-in per credential."—— Secrets management · OpenClaw Docs
明文仍然能用,SecretRef 是"按需开启"的,不是强制的。
这个态度很重要。它意味着你有选择权,不用为了安全把整套系统推倒重来。官方推荐的是"渐进式迁移",而不是一刀切。
二、SecretRef 到底长什么样
官方给出了一个"到哪都一样"的统一结构:
{ source: "env" | "file" | "exec" | "store", provider: "default", id: "..." }你原来在配置里写的是 "apiKey": "sk-xxxxxxxx"(明文),迁移后变成:
"apiKey": { "source": "store", "provider": "default", "id": "OPENAI_API_KEY" }区别就是:配置里不再放"钥匙本身",只放"保险箱的编号"。
官方支持四种来源,各有适用场景:
- env
从环境变量读,最轻量。写法: { source: "env", provider: "default", id: "OPENAI_API_KEY" },也可以简写成"${OPENAI_API_KEY}" - file
从某个 JSON 文件里按路径读( id是 JSON pointer) - exec
外部命令/密码管理器动态提供,适合 1Password、Vault 这类 - store
从 OpenClaw 自带的共享密钥库读——这也是我推荐日常用的那一种
三、共享密钥库(Shared secret store):放哪里?
这是本文的重点。官方在 Shared secret store 一节定义了它:
"The shared secret store is a Gateway-wide, team-scoped place for secrets and environment values that should be available to every Gateway process using the same state database."—— Secrets management · OpenClaw Docs
它是一个 Gateway 级别、团队作用域的密钥存放处,管理方式有两种:Control UI 的 Settings → Secrets,或者命令行 openclaw secrets store。
关键点在于,官方把它分成两种访问模式,这个区分特别实用:
kind: secret)保存后只写不可读。kind: "secret") values are write-only after saving." 列出、界面、命令行 get 都不会返回它的值,也没有"显示"接口。openclaw secrets store get <NAME>会直接拒绝,退出码 2。kind: env)store list/store get能读回来。适合那些本来就不敏感、只是想让子进程用到的配置。日常凭据(API Key、Token)应该用 secret模式——存进去就取不回来,从机制上杜绝了"页面上不小心看到明文"的可能。
⚠️ 一个官方特别标注的诚实提醒:密钥库的值在磁盘上不是加密的,它以未加密形式存在共享状态数据库 state/openclaw.sqlite里,靠的是文件0600、目录0700这类权限保护。如果你的威胁模型需要更强的存储隔离,官方建议改用外部 exec provider(如 1Password 或 Vault 插件)。
这一点我认为必须如实转述——没有神话它。官方自己说了"not encrypted at rest",我们不替它吹。
四、值是怎么被读取的?运行时快照机制
存好了之后,Gateway 怎么用这个值?官方在 Runtime model 里给了一个很关键的描述:
"Secrets resolve into an in-memory runtime snapshot, eagerly during activation, not lazily on request paths."—— Secrets management · OpenClaw Docs
用大白话拆解这条链路:
为什么这样设计?官方解释:这样能把"密钥提供者的故障"挡在热请求路径之外("This keeps secret-provider outages off hot request paths")。密钥源出问题,不会拖垮每一次调用。
五、模型 Key 的额外一层:出网哨兵(Sentinel)
如果你在意的是模型 provider 的 Key(OpenAI、DeepSeek、MiniMax 这类),官方还上了一层更细的保护,叫 Egress-time injection (sentinels):
"For model-provider credentials backed by SecretRefs, OpenClaw mints an opaque, process-local sentinel during model-auth resolution."—— Secrets management · OpenClaw Docs
机制是:系统先铸一个"哨兵"字符串(形如 oc-sent-v2.<...>.end),认证存储、流选项、SDK 配置、日志、错误对象看到的都是哨兵,不是真值;只有请求真正要离开进程的那一刻,才在 URL 和 Header 里把哨兵换回真值("immediately before each request leaves the process")。
更狠的是 fail-closed 设计:遇到无法识别的哨兵形态,直接拒绝发送,宁可失败也不把未解析的哨兵发给 provider。
这样一来,模型调用链路上大部分环节(日志、报错、SDK 配置)暴露的都是哨兵,真值只出现在最后的适配器边界。官方也诚实地补了一句:"they are not process isolation"——哨兵不是进程隔离,真值仍然存在于同进程内存中。还是那句话:如实转述,不夸大。
六、官方推荐的迁移流程(三步走)
前面讲了原理,这里给可直接照着做的流程。官方在 Audit and configure workflow 里明确列出"默认操作流程":
第一步:审计当前状态
openclaw secrets audit --check这个命令会找出:配置文件里的明文、SQLite 认证档案行里的明文、.env里的明文、生成文件里的敏感残留、无法解析的引用等。
第二步:配置并应用 SecretRef
openclaw secrets configure --apply交互式助手,会帮你配置 Provider、选择要保护的字段、采集 SecretRef 细节、做预检并应用。
第三步:重新审计
openclaw secrets audit --check这里有一条官方划的重点,我认为是整篇文档最重要的一句话:
"Do not treat the migration as complete until the re-audit is clean. If the audit still reports plaintext values at rest, the agent-access risk remains even when runtime APIs return redacted values."—— Secrets management · OpenClaw Docs
只要重新审计还能查到明文,迁移就没完成。因为即使运行时接口显示的是打码值,磁盘上的明文依然会被 agent 读取——这是一个"agent-access boundary(代理访问边界)"的问题,不是靠接口打码能解决的。
七、命令行实操:怎么安全地写入
如果你更习惯用命令行,官方 Secrets CLI 里有两个我认为特别值得记住的点。
第一,secret 类型的值不允许用 --value直接写在命令里。
官方给了明确理由:命令行参数会通过 shell 历史记录和进程列表泄露。所以正确姿势是三种安全输入之一:
管道输入(stdin 非 TTY 时) --value-file <path>从文件读 交互式的无回显提示(no-echo prompt)
举个官方示例,从 1Password 读取并写入:
op read 'op://Engineering/OpenAI/apiKey' | \ openclaw secrets store set OPENAI_API_KEY --kind secret第二,改了值之后要重载运行时快照。
openclaw secrets reload官方说明:直接命令行写入是本地操作,改完之后要跑一次 reload,让"活跃的内存快照"跟上("so the active in-memory snapshot picks it up")。好消息是——这是原子重载,不需要重启整个服务。
八、给"关键入口"留一条后路
最后说一段文档里没有明说、但我在实践中体会很深的原则。
官方文档提到了一个概念叫"fail-closed owners"——解析失败就拒绝执行的组件。登录 token(gateway.auth.token)就属于这一类。
这意味着:如果你把它也强行 SecretRef 化,一旦解析环节出任何问题(比如值不可取回、源不可用),你就会被锁在自己系统外面。而密钥库里的 secret又是"只写不可读"的——存进去就再也取不出来。
所以我的结论是:关键入口密钥,保留明文的可用性,可能比追求"全盘加密"更重要。这是一种工程权衡,而不是偷懒。文档也支持这个思路——它一再强调"Plaintext still works",就是承认现实世界的可用性需求。
这一点和官方"渐进式、按需迁移"的立场是完全一致的。
总结
把这次实践的核心结论浓缩成一张卡片:
- 官方立场
明文可用,SecretRef 是推荐但可选的增强("Plaintext still works") - 推荐做法
日常凭据用共享密钥库的 secret模式(只写不可读),配置里只放 SecretRef 指针 - 读取机制
启动时 eager 解析进内存快照,请求只读快照,日志自动打码,模型 Key 还有出网哨兵 - 迁移流程
audit --check→configure --apply→ 再次audit --check,审计干净才算完成 - 诚实边界
密钥库的值"not encrypted at rest",靠文件权限保护;哨兵"不是进程隔离" - 可用性权衡
关键入口(登录 token)建议保留明文,别把自己锁在门外
密钥管理这件事,最怕的不是"做得不够",而是"为了做得漂亮,把系统搞挂"。官方的态度其实很务实:能安全就安全,但要为可用性留余地。
希望这篇能帮你少踩一个坑。
参考资料
OpenClaw 官方文档 · Secrets management:https://docs.openclaw.ai/gateway/secrets OpenClaw 官方文档 · Secrets CLI:https://docs.openclaw.ai/cli/secrets OpenClaw 官方文档 · Secrets 工具:https://docs.openclaw.ai/tools/secrets OpenClaw 官方文档 · Secrets apply plan contract:https://docs.openclaw.ai/gateway/secrets-plan-contract
















































































