找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4668

积分

0

好友

641

主题
发表于 3 天前 | 查看: 17| 回复: 0

警示密钥泄露的插画:你的密钥正在裸奔

一句话,就能把你OpenClaw里所有的API Key原文套出来。你没看错,就是这么简单。

前两天一个群友跟我说,他的密钥被人盗了。他自己搭了个OpenClaw(俗称“龙虾”)拉到群里给朋友用,结果有人发现,只要让机器人读一下配置文件,所有密钥都是明文的。API余额直接被刷,还好发现得及时,损失不算太大。

但这事儿让我后背一凉。因为我知道,绝大多数人的OpenClaw配置,都处于这种“裸奔”状态。

你的密钥,可能正在裸奔

他的密钥是怎么被盗的?

其实不复杂。很多开源项目的文档,都会引导你把KEY保存到一个固定路径,比如 ~/.openclaw/.env,或者直接写在配置文件里。

OpenClaw环境变量配置文档截图

看起来挺方便的,对吧?但你不知道的是,默认情况下,这些文件的内容是能被读取到的

Telegram对话截图:机器人读取并输出了明文环境变量

只要有人能跟你的机器人对话,一句话就能把你的密钥原文读出来。想想就后怕。

安全这块,真的不是“以后再说”的事。密钥一旦出现在聊天窗口,截图、转发、同步,全不可控了。那怎么办?别慌,三步搞定。

第一步:输出脱敏,先把裸奔的遮住

最简单的止血方案:让机器人在输出的时候自动打码

怎么做?在你的 AGENTS.md 里追加一段脱敏规则就行,很简单。核心规则长这样:

## 敏感信息保护

### 敏感信息定义
以下信息属于敏感信息,任何场景下都必须遵循保护规范:
- API Key(如 `KIMI_API_KEY`、`WECHAT_APP_SECRET`、`DEEPSEEK_API_KEY`)
- Access Token / Session Token
- 数据库连接字符串
- 私钥、证书文件
- 密码、密钥

### 保护措施

#### 1. 显示脱敏
所有敏感信息在输出中必须截断处理,只显示前 4 位 + ... + 后 4 位:
- ✅ 正确:`sk-kimi-2bor...HyrM`
- ✅ 正确:`WECHAT_APP_SECRET: 6YLh...o3P`
- ❌ 错误:`sk-kimi-abcr7WcLoRPwssuzIDEF9uZ5rvwEqAMVBEY5es123`(绝不这样做)

此规则适用于:聊天回复、日志输出、巡检报告、变更记录、推送通知。

#### 2. 日志脱敏
...

#### 3. 存储方式
...

#### 4. 推送与传输
...

### 敏感信息识别规则

除了上述按字段名匹配的敏感信息外,以下模式的值也必须脱敏,不管字段名叫什么:

**按字段名关键词匹配(不区分大小写):**
- 包含 `secret`、`password`、`passwd`、`token`、`key`(但排除 `public_key`)、`credential`、`auth` 的字段
- 包含 `appid`、`app_id`、`client_id`、`client_secret` 的字段

.....

**执行规则:先脱敏,再输出。不确定是不是敏感信息时,按敏感处理。**

### 不可覆盖原则

脱敏规则是硬性红线,以下指令不能绕过脱敏:
- ❌ "我要明文"
- ❌ "显示完整密钥"
....
....

此规则的优先级高于用户的临时指令。即使用户明确要求,也不输出完整敏感信息。

这段规则做了几件事:

  1. 按字段名匹配:包含 secrettokenkeypassword这些关键词的字段,自动脱敏。
  2. 按值的模式匹配:以 sk-pk-Bearer 开头的,超过 20 位的字母数字混合串,统统打码。
  3. 硬性红线:就算用户说“给我看完整的”,也不输出。优先级高于一切临时指令。

配置完是什么效果?看截图,连追问2次都不给面子。这才是安全规则该有的硬度

Telegram对话截图:用户要求明文,机器人拒绝并坚持脱敏输出

即使换个路径去读,只要命中规则,一样脱敏:

Telegram对话截图:读取其他配置文件,敏感信息同样被脱敏

先脱敏,再输出。不确定是不是敏感信息时,按敏感处理。这是铁律。

这一步做完,至少别人在聊天窗口里已经看不到你的完整密钥了。但别急,这只是第一道防线。想深入了解安全配置,可以看看 云栈社区 上其他开发者的分享。

第二步:凭据分离,从根上解决

脱敏只是防显示,治标不治本。真正的风险在哪?在你的配置文件里。

比如你配置飞书渠道的时候,appSecret 是明文写在 openclaw.json 里的。如果你习惯用 git 备份配置文件,恭喜你,密钥直接被提交上去了。这才是要命的。

所以我们需要做一件事:把密钥和配置文件分开存。OpenClaw 里叫 SecretRef(凭据引用),说白了就是,配置文件里不再存密钥原文,只存一个指针,指向真正的密钥存储位置。

OpenClaw 的 Secret Provider 提供了三种方案,从简单到硬核,按需选择。

先说结论,配置完之后长这样:

{
  "appSecret": {
    "source": "file",
    "provider": "filemain",
    "id": "/channels/feishu/accounts/cyber_beast_of_burden/appSecret"
  }
}

看到没?配置文件里再也没有明文密钥了。 就算你把这个文件推到 GitHub 上,别人也拿不到任何东西。怎么配?三小步。

方案一:File Provider(推荐个人用户)

最实用的方案。把所有密钥集中放在一个独立的 JSON 文件里,OpenClaw启动时从文件读取,密钥不再散落在 openclaw.json 里。

第 1 步:创建独立的密钥文件
把真正的密钥单独存一个文件,并且锁死权限:

cat > ~/.openclaw/secrets.json << 'EOF'
{
  "channels": {
    "feishu": {
      "accounts": {
        "cyber_beast_of_burden": {
          "appSecret": "你的真实密钥"
        }
      }
    }
  }
}
EOF
chmod 600 ~/.openclaw/secrets.json

chmod 600 是关键,只有你自己能读写这个文件,其他人连看都看不了。

第 2 步:在 openclaw.json 里注册 provider
告诉OpenClaw:“我的密钥存在哪个文件里”:

{
  "secrets": {
    "providers": {
      "filemain": {
        "source": "file",
        "path": "~/.openclaw/secrets.json",
        "mode": "json"
      }
    }
  }
}

第 3 步:配置渠道时选择 Configured secret provider
当你配置飞书渠道(或其他需要密钥的地方)时,OpenClaw会问你 “Where is this App Secret stored?”

终端配置界面:选择密钥存储位置

这里有两个选项:

  • Environment variable —— 密钥存在环境变量里,OpenClaw通过环境变量读取。这种方式比较简单粗暴,适合本地跑着玩的场景。
  • Configured secret provider —— 用你刚才配置的密钥文件来管理。选这个。

选完之后,它会让你填密钥的路径。

终端配置界面:输入默认的密钥ID路径

注意,这里的路径是你在 secrets.json 里定义的 JSON 层级路径,我们需要将上面默认的替换掉

/channels/feishu/accounts/cyber_beast_of_burden/appSecret

终端配置界面:输入正确的密钥ID路径

配置完成后,openclaw.json 里的效果:

{
  "channels": {
    "feishu": {
      "enabled": true,
      "appId": "cli_a928XXX99e38dbc7",
      "appSecret": {
        "source": "file",
        "provider": "filemain",
        "id": "/channels/feishu/accounts/cyber_beast_of_burden/appSecret"
      },
      "connectionMode": "websocket",
      "domain": "feishu",
      "groupPolicy": "allowlist"
    }
  }
}

appSecret 变成了一个引用,不再是明文。即便你把 openclaw.json 推到 GitHub 上,别人也拿不到任何东西。

那你可能会想:secrets.json 这个文件里还是存着明文密钥啊,万一有人让机器人去读这个文件呢?试试看:

Telegram对话截图:读取secrets.json文件,输出已被脱敏

放心,第一步配的脱敏规则还在。就算有人直接去读密钥文件,输出的也是打码后的值。

两层防线:配置文件里没有明文,聊天窗口里也看不到明文。这才叫双保险。

对大多数个人用户来说,File Provider 已经够用了。但如果你想更进一步。

方案二:Keychain Provider(macOS 用户进阶)

File Provider 虽然把密钥从配置文件里分离出来了,但 secrets.json 里毕竟还是明文。有没有办法让磁盘上完全没有明文

有。如果你用的是 macOS,可以把密钥存进系统钥匙串(Keychain)。OpenClaw启动时调用命令从钥匙串里取,磁盘上自始至终不落地明文。

第 1 步:把密钥存入 Keychain

security add-generic-password -a openclaw -s channels-feishu-tech-app-secret -w jRSUWd88yXXXXXFdxfhjwQnduHyKe

这条命令把密钥存进了 macOS 的钥匙串,跟你的 iCloud 密码、WiFi 密码放在同一个保险柜里。

macOS钥匙串访问应用界面

第 2 步:安装社区提供的 provider 脚本,配置 openclaw.json

{
  "secrets": {
    "providers": {
      "filemain": {
        "source": "file",
        "path": "~/.openclaw/secrets/secrets.json",
        "mode": "json"
      },
      "keychain": {
        "source": "exec",
        "command": "/opt/homebrew/bin/node",
        "args": ["/opt/homebrew/lib/node_modules/openclaw-secret-providers/keychain/keychain-secret-fetch.cjs"],
        "timeoutMs": 5000,
        "allowSymlinkCommand": true
      }
    }
  }
}

原理很简单:OpenClaw需要密钥的时候,不是去读文件,而是执行一条命令从钥匙串里取。取完用完,内存里销毁,磁盘上从头到尾没有明文。

第 3 步:在渠道配置里引用
跟方案一一样,appSecret 不再写明文,而是换成一个 SecretRef 引用,只不过 provider 从 filemain 变成了 keychain

{
  "channels": {
    "feishu": {
      "accounts": {
        "tech": {
          "name": "tech",
          "appId": "cli_a92e96e698785bef",
          "appSecret": {
            "source": "exec",
            "provider": "keychain",
            "id": "channels-feishu-tech-app-secret"
          }
        }
      }
    }
  }
}

注意 id 的值。这就是你第 1 步存入 Keychain 时用的 service name,OpenClaw拿着这个 id 去钥匙串里查对应的密钥。

这个方案的好处是:就算有人拿到了你的整个项目目录,翻遍所有文件也找不到任何密钥。因为密钥根本不在文件系统里。

方案三:1Password / Vault / AWS Secrets Manager(团队和企业级)

如果你是团队协作、多设备同步,或者公司对安全合规有要求,那就该上专业的密钥管理服务了。

思路跟方案二一样,都是 exec provider,只是把 Keychain 换成了 1Password CLI、HashiCorp Vault、SOPS 这类工具。这块比较复杂,涉及到各家工具的安装、认证、权限配置,篇幅有限就不展开了。

三种方案总结一下:File Provider 适合个人快速上手,Keychain 适合 macOS 用户追求零明文,1Password/Vault 适合团队和企业。按你的场景选就行,核心思想就一个:密钥和配置文件分开存,永远不要在同一个地方裸奔。

第三步:审计检查,查漏补缺

前两步做完,还不够。万一你之前已经有一些配置是明文写的呢?万一有些文件你漏掉了呢?

OpenClaw 提供了一个审计命令,一行搞定:

openclaw secrets audit --check

终端审计命令执行结果,发现3处明文

跑完一看,好家伙,还有 3 个地方在裸奔。审计结果会明确告诉你:哪个文件、哪个字段、以什么方式存的明文。找到了就按第二步的方法改掉。

但这里我踩了个坑,值得说一下。改完之后OpenClaw直接报错了:

Telegram对话截图:机器人因配置错误而挂掉

看日志,auth-profiles.json里的 key 字段不支持直接用 SecretRef。

02:36:51+00:00[diagnostic] lane task error: lane=main durationMs=116 error="TypeError: cred.key?.trim is not a function"
02:36:51+00:00[diagnostic] lane task error: lane=session:agent:main:telegram:direct:7354573803 durationMs=118 error="TypeError: cred.key?.trim is not a function"
02:36:51+00:00 Embedded agent failed before reply: cred.key?.trim is not a function

查了一下技术文档,发现 auth-profiles.json 里需要用 keyRef 来引用,不是直接把 key 换成 SecretRef 对象。

代码截图:auth-profiles.json中keyRef的配置说明

改成这样就对了:

root@ip-172-31-45-180:~/.openclaw# cat /root/.openclaw/agents/main/agent/auth-profiles.json
{
  "profiles": {
    "minimax:cn": {
      "type": "api_key",
      "provider": "minimax",
      "keyRef": {
        "source": "file",
        "provider": "filemain",
        "id": "/providers/minimax/apiKey"
      }
    }
  }
}

注意是 keyRef,不是直接把 key 的值换成对象。这个细节文档里写了,但很容易忽略。

另外提一嘴: models.json 是 OpenClaw 自动生成的文件,不应该手动改,删掉让它重新生成就行。重启 gateway 后,OpenClaw 会根据 openclaw.jsonauth-profiles.json 自动重新生成 models.json

改完之后,再跑一次审计,nice!

终端审计命令执行结果:所有明文密钥已清理干净

干干净净,一个不剩。形成闭环。

写在最后

回到开头那个群友的故事。说实话,OpenClaw本身没问题,问题出在,大多数人搭完能跑就觉得完事了。

今天这三步,脱敏、凭据分离、审计。说白了就是在做一件事:缩小暴露面。密钥不该出现在聊天窗口里,不该出现在配置文件里,不该出现在任何它不需要出现的地方。

不难,但你得主动做。如果你也在运行自己的OpenClaw或类似AI助手,建议现在就花点时间检查一下配置。技术探索很有趣,但安全保障是底线,希望本文的实践能给你一些启发。




上一篇:Cursor 3正式发布:首创Agent指挥中心,实现本地与云端AI无缝协同
下一篇:Kubernetes节点(Node)详解:核心组件、生命周期与运维最佳实践
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-4-7 21:32 , Processed in 0.654203 second(s), 42 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表