为什么不能直接要密钥

普通做法让你把 sk-xxxx 直接发到聊天/命令里,这有三个问题:

  1. 明文留在对话和日志里,谁都能看到。
  1. 命令历史/进程列表可能捕获参数里的密钥。
  1. 共享出去的截图/转录会泄露。

OpenClaw 的 secrets 工具把这条路改成了:AI 只请求"缺哪个密钥、干什么用",你用掩码录入,值直送托管存储,模型自己都读不到明文。

secrets 的三个动作

| 动作 | 用途 |

|------|------|

| list | 先看已有哪些密钥的元信息(只看名字/用途,不看值) |

| request | 向用户申请一个缺失的密钥(用户掩码录入,值进存储) |

| delete | 删除某个条目 |

第一步:先 list,别重复申请

secrets({ action: "list" })

返回的是元数据(名字、allowedHosts、用途),不含值。如果已经有同用途的密钥,直接复用,不要重复申请。

第二步:request 缺失的密钥

secrets({
  action: "request",
  name: "OPENAI_API_KEY",
  kind: "secret",
  reason: "调用 OpenAI 图像接口生成文章封面",
  allowedHosts: ["api.openai.com"]
})

调用后,聊天里会出现一个掩码录入框,你填入真实密钥。值不会出现在任何聊天文本、命令参数或日志里,直接进托管 SecretRef 存储。

#### allowedHosts 的重要性

allowedHosts 声明这个密钥"只能被发往哪些主机"。即使将来有恶意指令想把它发到别处,网关会拦截——这是纵深防御,不是形式主义。只填真实需要的域名,如 api.openai.com、dashscope.aliyuncs.com。

第三步:用返回的 SecretRef 落地配置

request 成功后,工具返回一个 SecretRef(如 ${OPENAI_API_KEY} 形式的引用)。把这个引用写进配置字段,而不是把真实值写进去:

  • 支持的配置项会识别 SecretRef,运行时自动用真实值替换。
  • 网关自己跑的命令,则用自动注入的不透明环境变量,AI 看不到、打印不出来。

关键行为(必须知道)

  1. AI 永远读不到值:request 返回的是引用,不是明文。这是设计铁律。
  1. list 只给元数据:已经存在的密钥,先查再决定要不要新申请。
  1. allowedHosts 为空 = 禁止外发:如果你没填,密钥只能被本机配置引用,无法 egress 替换,安全但不便——按需填真实域名。
  1. Operator 设的环境变量可读但不应申请:已有的 env 条目是人工配的,别用 request 去覆盖它。

标准工作流

  1. 脚本要一个密钥 → 先 secrets({action:"list"}) 查重。
  1. 没有 → secrets({action:"request", name, reason, allowedHosts})。
  1. 用户在掩码框录入 → 拿到 SecretRef。
  1. 把 SecretRef 写进对应配置字段,重新跑任务。

踩坑与反模式

  • ❌ 在聊天里发 sk-xxx:明文泄露,且违反安全策略。走 request。
  • ❌ **allowedHosts 乱填 ***:等于放弃主机白名单保护,按需精确填。
  • ❌ 重复申请相同密钥:先 list 复用,别每跑一次都要用户录一遍。
  • ✅ 所有外部 API Key 一律走 secrets:模型、日志、截图里都不会出现明文。

和 exec 的分工

  • 本机 shell / node / sandbox:没有 SecretRef 自动注入机制,密钥需手动管理(更危险)。
  • 网关托管命令:自动注入 opaque env,AI 不可见,最安全。

一句话总结

要密钥别张口要明文。先 list 查重,再 request 让用户掩码录入,拿 SecretRef 写配置——AI、日志、截图里全程零明文。


扫码关注公众号,获取更多 OpenClaw 实操技巧:

公众号二维码