【养虾记12】OpenClaw密钥安全加固实战
大家好,我是质量架构师东哥,专注软件研发过程中的质量和效率改进。
讲清原理,打通实战,拒绝空谈。欢迎关注。
背景
在上一讲中我们讲解了如何制定安全规范【养虾记11】六步搞定OpenClaw安全规范,但是发现小龙虾经过我们的PUA之后还是可能存在不遵守安全规范暴露api key 的情况,本篇是上一篇的进阶,如何做到更加安全的保护API key的安全性。

原理

OpenClaw 的 openclaw.json 和 agents/*/agent/models.json 默认以明文存储 API Key,存在泄漏风险。
OpenClaw 原生支持 SecretRef 机制——所有 apiKey、appSecret、clientSecret、token 字段可用引用对象替代明文:
// 明文(不安全)
"apiKey": "your-plaintext-key"
// SecretRef(推荐)
"apiKey": {
"source": "env", // 来源类型:env | file | exec
"provider": "default", // 对应 secrets.providers 中注册的 provider 别名
"id": "ENV_VAR_NAME" // 环境变量名(必须匹配 ^[A-Z][A-Z0-9_]{0,127}$)
}
运行时 OpenClaw 通过 process.env[ref.id] 读取实际值。

步骤 1:创建环境变量文件
需要创建两个文件,内容相同但格式不同——这是因为交互式 shell 和 systemd 服务读取环境变量的方式不同。
1.1 交互式 shell 用(带 export)
手动执行 openclaw CLI 命令时需要环境变量,通过 /etc/profile.d/*.sh 在登录时自动加载。
cat > /etc/profile.d/openclaw-secrets.sh << 'EOF'
#!/bin/bash
# OpenClaw secrets — loaded at login. Keep chmod 600.
# Model providers
export OPENCLAW_STREAMLAKE_API_KEY="xxx"
export OPENCLAW_VOLCENGINE_API_KEY="xxx"
# Gateway
export OPENCLAW_GATEWAY_TOKEN="xxx"
# Feishu accounts
export OPENCLAW_FEISHU_DEFAULT_SECRET="xxx"
export OPENCLAW_FEISHU_IP_SECRET="xxx"
export OPENCLAW_FEISHU_LOBSTER_SECRET="xxx"
export OPENCLAW_FEISHU_DEVELOPER_SECRET="xxx"
export OPENCLAW_FEISHU_DEVOPS_SECRET="xxx"
export OPENCLAW_FEISHU_IELTS_SECRET="xxx"
export OPENCLAW_FEISHU_QA_SECRET="xxx"
# DingTalk accounts
export OPENCLAW_DINGTALK_CHEF_SECRET="xxx"
export OPENCLAW_DINGTALK_WORK_SECRET="xxx"
# Tavily web search
export TAVILY_API_KEY="xxx"
EOF
chmod 600 /etc/profile.d/openclaw-secrets.sh
1.2 systemd 服务用(不带 export)
踩坑警告:systemd 服务进程有独立的运行环境,不会读取
/etc/profile.d/*.sh。 如果只创建.sh文件而不创建.env文件,gateway 启动时将无法解析 SecretRef,导致服务启动失败。
systemd 的 EnvironmentFile 要求 KEY=VALUE 格式(不带 export 前缀):
cat > /etc/profile.d/openclaw-secrets.env << 'EOF'
# OpenClaw secrets for systemd service. Keep chmod 600.
OPENCLAW_STREAMLAKE_API_KEY=xxx
OPENCLAW_VOLCENGINE_API_KEY=xxx
OPENCLAW_GATEWAY_TOKEN=xxx
OPENCLAW_FEISHU_DEFAULT_SECRET=xxx
OPENCLAW_FEISHU_IP_SECRET=xxx
OPENCLAW_FEISHU_LOBSTER_SECRET=xxx
OPENCLAW_FEISHU_DEVELOPER_SECRET=xxx
OPENCLAW_FEISHU_DEVOPS_SECRET=xxx
OPENCLAW_FEISHU_IELTS_SECRET=xxx
OPENCLAW_FEISHU_QA_SECRET=xxx
OPENCLAW_DINGTALK_CHEF_SECRET=xxx
OPENCLAW_DINGTALK_WORK_SECRET=xxx
TAVILY_API_KEY=xxx
EOF
chmod 600 /etc/profile.d/openclaw-secrets.env
1.3 立即加载到当前 shell
source /etc/profile.d/openclaw-secrets.sh
步骤 2:修改 systemd 服务配置
在 ~/.config/systemd/user/openclaw-gateway.service 的 [Service] 段中,在所有 Environment= 行之前添加:
EnvironmentFile=/etc/profile.d/openclaw-secrets.env
修改后的 [Service] 段开头应形如:
[Service]
ExecStart=/usr/bin/node ...
Restart=always
...
EnvironmentFile=/etc/profile.d/openclaw-secrets.env
Environment=HOME=/root
Environment=TMPDIR=/tmp
...
添加后重新加载 systemd 配置:
systemctl --user daemon-reload
步骤 3:注册 secrets provider
在 openclaw.json 中声明一个 env 类型的 secrets provider,并指定允许读取的环境变量白名单:
openclaw config set secrets.providers.default \
--provider-source env \
--provider-allowlist OPENCLAW_STREAMLAKE_API_KEY \
--provider-allowlist OPENCLAW_VOLCENGINE_API_KEY \
--provider-allowlist OPENCLAW_GATEWAY_TOKEN \
--provider-allowlist OPENCLAW_FEISHU_DEFAULT_SECRET \
--provider-allowlist OPENCLAW_FEISHU_IP_SECRET \
--provider-allowlist OPENCLAW_FEISHU_LOBSTER_SECRET \
--provider-allowlist OPENCLAW_FEISHU_DEVELOPER_SECRET \
--provider-allowlist OPENCLAW_FEISHU_DEVOPS_SECRET \
--provider-allowlist OPENCLAW_FEISHU_IELTS_SECRET \
--provider-allowlist OPENCLAW_FEISHU_QA_SECRET \
--provider-allowlist OPENCLAW_DINGTALK_CHEF_SECRET \
--provider-allowlist OPENCLAW_DINGTALK_WORK_SECRET \
--provider-allowlist TAVILY_API_KEY
步骤 4:替换 openclaw.json 中的明文密钥
使用 openclaw config set 将每个密钥字段替换为 SecretRef 引用:
# 大模型 API Key
openclaw config set models.providers.custom-xxx-streamlakeapi-com.apiKey \
--ref-source env --ref-provider default --ref-id OPENCLAW_STREAMLAKE_API_KEY
openclaw config set models.providers.custom-ark-cn-beijing-volces-com.apiKey \
--ref-source env --ref-provider default --ref-id OPENCLAW_VOLCENGINE_API_KEY
# Gateway 认证 Token
openclaw config set gateway.auth.token \
--ref-source env --ref-provider default --ref-id OPENCLAW_GATEWAY_TOKEN
# 飞书 appSecret(7 个账号)
openclaw config set channels.feishu.accounts.default.appSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_DEFAULT_SECRET
openclaw config set channels.feishu.accounts.feishu-ip.appSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_IP_SECRET
openclaw config set channels.feishu.accounts.feishu-lobster.appSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_LOBSTER_SECRET
openclaw config set channels.feishu.accounts.feishu-developer.appSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_DEVELOPER_SECRET
openclaw config set channels.feishu.accounts.feishu-devops.appSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_DEVOPS_SECRET
openclaw config set channels.feishu.accounts.feishu-ielts.appSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_IELTS_SECRET
openclaw config set channels.feishu.accounts.feishu-qa.appSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_FEISHU_QA_SECRET
# 钉钉 clientSecret(2 个账号)
openclaw config set channels.dingtalk-connector.accounts.dingtalk-chef.clientSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_DINGTALK_CHEF_SECRET
openclaw config set channels.dingtalk-connector.accounts.dingtalk-work.clientSecret \
--ref-source env --ref-provider default --ref-id OPENCLAW_DINGTALK_WORK_SECRET
替换后配置文件中的字段变为:
"apiKey": {
"source": "env",
"provider": "default",
"id": "OPENCLAW_STREAMLAKE_API_KEY"
}
步骤 5:替换 agents/*/agent/models.json 中的明文密钥
9 个 agent 目录下的 models.json 文件包含重复的 API Key,通过脚本批量替换:
python3 << 'PYEOF'
import json, glob
secret_ref_streamlake = {"source": "env", "provider": "default", "id": "OPENCLAW_STREAMLAKE_API_KEY"}
secret_ref_volcengine = {"source": "env", "provider": "default", "id": "OPENCLAW_VOLCENGINE_API_KEY"}
for f in sorted(glob.glob("/root/.openclaw/agents/*/agent/models.json")):
with open(f) as fp:
data = json.load(fp)
changed = False
for name, provider in data.get("providers", {}).items():
if "apiKey" not in provider or not isinstance(provider["apiKey"], str):
continue
# 跳过本地占位 key(如 "ollama-local")
if provider["apiKey"] in ("ollama-local",):
continue
# 根据 provider 名称匹配对应的 SecretRef
if "streamlake" in name:
provider["apiKey"] = secret_ref_streamlake
changed = True
elif "ark" in name or "volces" in name:
provider["apiKey"] = secret_ref_volcengine
changed = True
if changed:
with open(f, "w") as fp:
json.dump(data, fp, indent=2, ensure_ascii=False)
fp.write("\n")
print(f"Updated: {f}")
else:
print(f"Skipped: {f}")
PYEOF
涉及的文件(共 9 个):
agents/main/agent/models.json
agents/ip/agent/models.json
agents/lobster/agent/models.json
agents/developer/agent/models.json
agents/devops/agent/models.json
agents/ielts/agent/models.json
agents/qa/agent/models.json
agents/chef/agent/models.json
agents/work/agent/models.json
步骤 6:清理 .env 文件
从 ~/.openclaw/.env 中移除已转移到系统环境变量的 TAVILY_API_KEY。
保留 DINGTALK_MCP_* URL(MCP 网关签发的 URL,key 嵌入在 URL 中无法拆分,且 .env 已被 .gitignore 排除)。
# 编辑 .env,删除 TAVILY_API_KEY 行
sed -i '/^TAVILY_API_KEY=/d' ~/.openclaw/.env
步骤 7:删除备份文件
openclaw config set 会自动创建 openclaw.json.bak(含旧明文),需手动删除:
rm -f ~/.openclaw/openclaw.json.bak
.gitignore 中的 *.bak 规则可防止备份文件被意外提交。
步骤 8:重建 git 仓库
如果旧 git 历史中包含明文密钥,且不需要保留历史记录,直接重新初始化:
cd ~/.openclaw
rm -rf .git
git init
git add .
git commit -m "feat: 初始化(密钥已外部化到环境变量)"
步骤 9:验证
# 1. 验证配置文件合法性
openclaw config validate
# 预期输出: Config valid: ~/.openclaw/openclaw.json
# 2. 确认配置文件和 git 历史中无明文密钥残留
grep -rn "your-actual-key-value" ~/.openclaw/ --exclude-dir=.git
git grep "your-actual-key-value"
# 预期: 两条命令均无输出
# 3. 重启 gateway 并检查状态
systemctl --user restart openclaw-gateway.service
systemctl --user status openclaw-gateway.service
# 预期: Active: active (running)
# 4. 健康检查
curl -s http://127.0.0.1:18789/health
# 预期输出: {"ok":true,"status":"live"}
最终文件布局

/etc/profile.d/
├── openclaw-secrets.sh # 交互式 shell 环境变量(chmod 600)
└── openclaw-secrets.env # systemd EnvironmentFile 格式(chmod 600)
~/.config/systemd/user/
└── openclaw-gateway.service # 添加了 EnvironmentFile 指令
~/.openclaw/
├── openclaw.json # 所有密钥字段改为 SecretRef 对象
├── .env # 仅保留 DINGTALK_MCP_* URL(gitignored)
└── agents/*/agent/models.json # apiKey 改为 SecretRef 对象
环境变量与用途对照表
日常维护
新增密钥:
-
在
/etc/profile.d/openclaw-secrets.sh和.env中添加新变量 -
openclaw config set secrets.providers.default --provider-allowlist NEW_VAR ...(需带上所有已有的 allowlist 项) -
openclaw config set <config.path> --ref-source env --ref-provider default --ref-id NEW_VAR -
systemctl --user daemon-reload && systemctl --user restart openclaw-gateway.service
轮换密钥:
-
在对应平台生成新密钥
-
更新
/etc/profile.d/openclaw-secrets.sh和.env中的值 -
source /etc/profile.d/openclaw-secrets.sh -
systemctl --user restart openclaw-gateway.service -
配置文件无需任何改动
如果你觉得这篇文章写的还不错,麻烦双击屏幕点个👍、点个❤️、点个转发,你的支持是我继续分享的动力!
欢迎志同道合的朋友添加我的微信 Miller_Shan 一起交流,互相学习,共同进步。

夜雨聆风