一、事情的起因

事情要从一枚戒指说起。

我给 OpenClaw 写了一个 Oura Ring 健康数据同步的 Skill——每天自动拉取睡眠、心率、活动数据,生成健康日报。Skill 写好了,需要配置 Oura 的 Personal Access Token(PAT)。

按照以前的习惯,我准备直接在 Telegram 里把 token 贴给我的 Agent(没错,就是那只龙虾),让它帮我配到环境变量里。

手指悬在发送键上,突然犹豫了。

“等一下,我把 API Key 直接发到 Telegram 里,这安全吗?”

我问了龙虾。它说:不太安全,建议我看看 OpenClaw 的 SecretRef 机制,支持从 1Password 读取密钥。

这一查不得了。不只是"不太安全"——简直是在裸奔。

二、你的 API Key 发出去之后都去了哪

第一站:Telegram 服务器

你和 Bot 的对话不是端到端加密的(那是 Secret Chat 的专利)。你发出去的每一条消息——包括那串 API Key——都会以明文形式永久存储在 Telegram 的云端服务器上。

“但我可以删消息啊?"——你确定 Telegram 真的从所有备份和副本中物理删除了?

第二站:LLM 的上下文窗口

你的 API Key 不只是"发给了 Agent”。它进入了大语言模型的上下文窗口,成了一段 token。如果你用的是云端模型(大概率是),这意味着你的密钥被发送到了模型提供商的 API 端点。

虽然主流模型提供商声称不会用 API 调用数据训练模型,但这串 key 确实经过了他们的服务器、在他们的内存里被处理过。如果对方出了安全事故呢?

第三站:本地日志和会话存储

OpenClaw 在本地有日志(~/.openclaw/logs/gateway.log),也有会话存储。你在对话里发的 API Key,会作为消息内容被记录下来。任何能访问你电脑的人——或者你自己不小心把日志打包发出去——都能看到。

第四站:openclaw.json

就算 Agent 帮你配好了,那串 key 最终也是以明文躺在 ~/.openclaw/openclaw.json 里:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "models": {
    "providers": {
      "zenmux-claude": {
        "apiKey": "sk-ss-v1-6a538d..."
      }
    }
  },
  "channels": {
    "telegram": {
      "botToken": "85217684:AAH..."
    }
  }
}

Time Machine 备份?iCloud 同步?不小心 cat 到屏幕上?一个配置文件,十几个明文密钥,整整齐齐等着被泄露。

你的 API Key 至少在四个地方留下了痕迹:Telegram 云端、LLM 服务器、本地日志、配置文件。

别傻了。

三、发现 OpenClaw 早就准备好了工具

回到 Oura 的事。龙虾让我去翻 OpenClaw 的文档,我发现官方原生支持一套叫 SecretRef 的密钥管理机制——不是第三方插件,不用额外安装,就在核心里。

思路很简单:配置文件里不再直接放密钥的值,而是放一个"指针",告诉 OpenClaw 去别处拿真正的密钥。

我又追问龙虾:“那 openclaw.json 里的十几个密钥,是不是全都能这样处理?”

龙虾帮我查了一圈文档,结论是:

几乎全部都能迁。 SecretRef 支持的字段覆盖了:

  • 所有模型提供商的 apiKey 和自定义认证头
  • 所有搜索引擎的 apiKey(Brave、Perplexity、Grok、Gemini、Kimi)
  • 所有通信渠道的认证密钥(Telegram、Discord、Slack、Matrix、飞书……)
  • Gateway 认证 token / password(2026.3.7 版本新增!
  • TTS 的 API Key(ElevenLabs、OpenAI)
  • Cron webhook token、Skill 级别的 apiKey

不能迁的是系统自动生成/轮换的东西:OAuth 刷新令牌、Matrix 的 accessToken、WhatsApp 凭证文件。合理——这些本来就不该由人类手动管理。

简单说:你手动填进去的静态密钥,全都能迁。

特别值得一提的是:2026.3.7 版本开始,channels.telegram.botTokengateway.auth.token 也支持 SecretRef 了。 这是之前版本做不到的。这意味着你的 openclaw.json 可以实现 完全零明文密钥

四、为什么选 1Password

密钥要从 openclaw.json 搬出去,总得有个地方放。我选了 1Password——不是因为它最好,而是因为它最顺手

  1. 不需要额外搞一个系统 — 大部分人已经在用 1P 管理日常密码了,不用再学一套新东西
  2. 单独 Vault 做隔离 — 建个叫 Agent 的 Vault,专门放 AI Agent 用的密钥,和个人密码完全分开
  3. Vault 级别的只读权限 — 给 Agent 的 Service Account 只开 Agent vault 的读取权限,碰不到你的其他密码
  4. 随时随地管理 — 手机 App、电脑 App、网页端都能操作。新增一个 API Key?掏出手机在 Agent vault 里建一个 item,告诉你的龙虾"去 1P 拿"就行了。不用开电脑,不用碰终端,不用在聊天里发密钥
  5. 不需要新账户 — 用你现有的 1P 账户就行,加个 Vault 的事

两种集成方式

1Password CLI(op)提供了两种方式和 OpenClaw 集成:

方式一:桌面 App 集成(交互式)

在 1P 桌面 App 的设置里打开 “Integrate with 1Password CLI”,之后终端里每次调用 op 命令时,桌面 App 会弹出认证窗口——用指纹(Touch ID)、Face ID、Apple Watch 或系统密码授权。

安全性最高(每次操作都有人类授权),但不适合无人值守——你总不能让 Agent 每天凌晨 3 点叫你起来按指纹吧。

适合场景: 你自己在终端里手动管理密钥时用。

方式二:Service Account(无人值守)

1Password 支持创建 Service Account——一个不绑定人类账户的 token,专门给机器用:

  • 精确授权只能读取特定 Vault(比如只给 Agent vault 的只读权限)
  • 不能写入、不能删除、不能访问其他 Vault
  • token 存在本地文件里,Agent 自动读取,全程无需人类介入

适合场景: 让 Agent / Cron 任务无人值守地读取密钥。

我们选方式二。 日常管理(增删改密钥)在手机或电脑上操作 1P;Agent 只负责读取,用 Service Account 就够了。

五、实操开始

前置准备:安装 1Password CLI

macOS 用 Homebrew 一行搞定:

1
brew install --cask 1password-cli

验证安装:

1
2
op --version
# 输出类似:2.32.1

其他平台参考 1Password CLI 官方安装文档

第一步:在 1Password 里建一个专属 Vault

打开 1Password(手机 App 或电脑 App 都行):

  1. 点击左上角账户名旁边的 "+" 或进入 Settings → Vaults → New Vault
  2. 名称填 Agent(或任何你喜欢的名字)
  3. 创建完成

这个 Vault 专门放 AI Agent 用的密钥,和你的个人密码完全隔离。

第二步:创建 Service Account

这一步在网页端操作(手机也行,网页更方便):

  1. 打开 1Password.com 并登录
  2. 进入 Developer → Directory,在 Infrastructure Secrets Management 下选 Other,然后点 Create a Service Account
  3. 按向导操作:
    • Name:起个名字,比如 openclaw-agent
    • Create vaults:选 No(不需要创建 vault 的权限)
    • Vault access:选择你刚创建的 Agent vault,权限选 Read Items(只读)
  4. Create Account
  5. ⚠️ 重要! 页面会显示一个 Service Account Token——这个 token 只显示一次。点 Save in 1Password 把它保存到你的个人 vault 里(不是 Agent vault)

保存好了吗?确认一下。关了页面就再也看不到了。

第三步:把 Service Account Token 存到本地

在终端里执行:

1
2
3
4
5
# 把 Service Account Token 粘贴进来(替换引号里的内容)
echo "eyJhbGciOi...你的token..." > ~/.openclaw/.op-token

# 设置权限为 600(只有你自己能读写)
chmod 600 ~/.openclaw/.op-token

验证 token 能正常工作:

1
OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/.op-token) op vault list

能看到你的 Agent vault?配置成功。

第四步:先拿 Oura 试水

回到最初的问题。Oura 的 PAT 是非关键密钥——搞坏了最多是今天的睡眠数据晚同步几小时,Agent 核心功能不受影响。拿它来做第一个试验。

在 Agent vault 里创建 item:

打开 1Password → 切换到 Agent vault → 新建 item:

  • 类型:Password
  • 名称oura(随便起,不需要和代码里的变量名一致)
  • password 字段:粘贴你的 Oura Personal Access Token

验证能读到:

1
OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/.op-token) op read "op://Agent/oura/password"

吐出了 Oura token 的值。成了。

在 Cron 任务里直接用 1P 读取 token,传给同步脚本:

1
2
OURA_TOKEN=$(OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/.op-token) \
  op read "op://Agent/oura/password") python3 scripts/sync.py

数据正常同步。第一个密钥成功脱离明文——就是那个差点在 Telegram 里裸发的 Oura PAT。

第五步:胃口变大——清空 openclaw.json

Oura 验证可行之后,我盯上了 openclaw.json 里的十几个明文密钥。

5.1 写同步脚本

创建 ~/.openclaw/sync-secrets.sh——一个从 1P 批量同步到本地 JSON 的脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#!/bin/bash
export OP_SERVICE_ACCOUNT_TOKEN=$(cat ~/.openclaw/.op-token)

python3 -c "
import json, subprocess, os

def op_read(ref):
    try:
        result = subprocess.run(['op', 'read', ref],
                                capture_output=True, text=True, timeout=10,
                                env={**os.environ})
        return result.stdout.strip() if result.returncode == 0 else None
    except:
        return None

# 自动发现 Agent vault 中的所有 item
result = subprocess.run(
    ['op', 'item', 'list', '--vault', 'Agent', '--format=json'],
    capture_output=True, text=True, timeout=15, env={**os.environ})
items = json.loads(result.stdout) if result.returncode == 0 else []

secrets = {}
for item in items:
    title = item.get('title', '')
    val = op_read(f'op://Agent/{title}/password')
    if val:
        secrets[title] = val

with open(os.path.expanduser('~/.openclaw/secrets.json'), 'w') as f:
    json.dump(secrets, f, indent=2)
os.chmod(os.path.expanduser('~/.openclaw/secrets.json'), 0o600)
print(f'Synced {len(secrets)} secrets')
"

这个脚本有个巧妙之处:不硬编码密钥列表。它自动枚举 Agent vault 里的所有 item,逐个读取。以后在 1P 里新增一个密钥,跑一次脚本就自动同步。

1
chmod +x ~/.openclaw/sync-secrets.sh

5.2 在 1P Agent vault 里批量创建 item

打开 1Password,在 Agent vault 里逐个创建(一边喝咖啡一边操作,不用碰终端):

1P item 名称对应的 openclaw.json 字段
bravetools.web.search.apiKey
zenmux1models.providers.zenmux1-claude.apiKey
zenmux2models.providers.zenmux2-claude.apiKey
openclaw-apimodels.providers.openclaw-api.apiKey
perplexitytools.web.search.perplexity.apiKey
groktools.web.search.grok.apiKey
geminitools.web.search.gemini.apiKey
kimitools.web.search.kimi.apiKey

每个 item 的 password 字段填对应的 API Key 值。

5.3 同步到本地

1
2
~/.openclaw/sync-secrets.sh
# 输出:Synced 9 secrets

5.4 在 OpenClaw 里注册 File Provider

1
2
3
4
5
openclaw config set secrets.providers.op_file '{
  "source": "file",
  "path": "~/.openclaw/secrets.json",
  "mode": "json"
}'

5.5 先拿 Brave Search 试刀

和 Oura 时一样的策略——先迁最不关键的:

1
2
3
4
5
openclaw config set tools.web.search.apiKey '{
  "source": "file",
  "provider": "op_file",
  "id": "/brave"
}'

id: "/brave" 是 JSON Pointer,指向 secrets.json 里 key 为 brave 的值。

OpenClaw 自动热重载(不需要重启 Gateway),搜索功能正常。

5.6 批量替换剩余密钥

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# 模型提供商
openclaw config set models.providers.zenmux1-claude.apiKey \
  '{"source":"file","provider":"op_file","id":"/zenmux1"}'
openclaw config set models.providers.zenmux2-claude.apiKey \
  '{"source":"file","provider":"op_file","id":"/zenmux2"}'
openclaw config set models.providers.openclaw-api.apiKey \
  '{"source":"file","provider":"op_file","id":"/openclaw-api"}'

# 搜索引擎
openclaw config set tools.web.search.perplexity.apiKey \
  '{"source":"file","provider":"op_file","id":"/perplexity"}'
openclaw config set tools.web.search.grok.apiKey \
  '{"source":"file","provider":"op_file","id":"/grok"}'
openclaw config set tools.web.search.gemini.apiKey \
  '{"source":"file","provider":"op_file","id":"/gemini"}'
openclaw config set tools.web.search.kimi.apiKey \
  '{"source":"file","provider":"op_file","id":"/kimi"}'

每改一个,OpenClaw 自动热重载,功能无缝切换。

第六步:最后的堡垒——Telegram Bot Token 和 Gateway Auth

这两个是核心中的核心。Telegram token 断了就收不到消息,Gateway auth 断了连不上控制台。

2026.3.7 版本之前,这两个字段不支持 SecretRef——你被迫只能用明文。现在支持了。

在 1P Agent vault 里再创建两个 item:

1P item 名称password 值
botToken你的 Telegram Bot Token
gateway你的 Gateway Auth Token

同步 + 替换:

1
2
3
4
5
6
~/.openclaw/sync-secrets.sh

openclaw config set channels.telegram.botToken \
  '{"source":"file","provider":"op_file","id":"/botToken"}'
openclaw config set gateway.auth.token \
  '{"source":"file","provider":"op_file","id":"/gateway"}'

设完之后,消息收发正常,Gateway 连接正常。

至此,openclaw.json 里零明文密钥。

六、迁移前后对比

迁移前:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
  "models": {
    "providers": {
      "zenmux-claude": {
        "apiKey": "sk-ss-v1-6a538da7fd397b..."
      }
    }
  },
  "channels": {
    "telegram": {
      "botToken": "85217684:AAH..."
    }
  }
}

迁移后:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
  "models": {
    "providers": {
      "zenmux-claude": {
        "apiKey": {
          "source": "file",
          "provider": "op_file",
          "id": "/zenmux1"
        }
      }
    }
  },
  "channels": {
    "telegram": {
      "botToken": {
        "source": "file",
        "provider": "op_file",
        "id": "/botToken"
      }
    }
  }
}

干干净净。这个文件现在可以放心备份、甚至公开分享结构——里面看不到任何密钥。

七、日常工作流

迁移完成后,日常操作变得很轻松:

新增一个 API Key:

  1. 手机上打开 1Password → Agent vault → 新建 item → 填入密钥
  2. 回到电脑,跑一次 ~/.openclaw/sync-secrets.sh
  3. openclaw config set 加一个 SecretRef 指向它
  4. 完事。全程不在聊天里发任何密钥

换一个 API Key:

  1. 在 1P 里改 item 的值
  2. 跑一次同步脚本
  3. OpenClaw 自动热重载。不用重启,不用改配置

新电脑迁移:

  1. 拷贝 openclaw.json(里面全是 SecretRef,没有明文,放心拷)
  2. 拷贝 .op-token
  3. 跑一次同步脚本
  4. 搞定

八、安全设计细节

OpenClaw 在密钥管理上有几个值得一提的设计:

  • 启动时 fail-fast:任何活跃的 SecretRef 解析失败 → Gateway 直接拒绝启动,不带着残缺密钥运行
  • 热重载原子交换:全部解析成功才切换新快照,任何一个失败就保持旧的
  • 活跃表面过滤:只验证当前真正在用的密钥。配了 5 个搜索引擎但只启用了 Brave?其他 4 个解析失败不阻塞启动
  • config get 自动 redactopenclaw config get 显示 SecretRef 字段时会输出 __OPENCLAW_REDACTED__,防止泄露

九、还没搞定的部分

~/.zshrc 里还有些环境变量(ANTHROPIC_API_KEYGEMINI_API_KEY 等),这些是给 Claude Code 等外部工具用的,暂时没动。迁移它们需要用 op run -- 命令 包裹执行,改动面较大,以后再说。

总结

项目数量
迁移到 1P 的密钥12+
openclaw.json 残留明文0
整个过程耗时~30 分钟

一句话总结:别再在聊天里发 API Key 了。1Password 当保险柜,sync 脚本当搬运工,SecretRef 当钥匙卡。30 分钟搞定,从此 openclaw.json 里干干净净。

你的 AI Agent 帮你管理着越来越多的东西。它的配置文件里密钥的含金量,可能比你想象的高得多。

花 30 分钟,给它装个保险柜。别傻了。