hermes dump 贴出去求助,可能连你自己的 API Key 一起贴出去了


凌晨一点,你的 cron 任务又失败了。你按老规矩跑 hermes dump,把输出连同几段日志贴进 Discord 频道求助——半小时后有人私信你:“兄弟,你的 OpenRouter key 还在第一段里。”

这不是危言耸听。hermes dump 的设计初衷就是“一段可以直接复制粘贴的纯文本摘要”,而恰恰是那段最方便粘贴的文字,曾经把备用 provider 的 API Key原样打印出来。补丁在 2026-09-29 合并进 main(PR #127207,收编了 #126132 与 #126168),把脱敏从“上传时做一层”改成了“生成时做一层、上传时再做一层”。

先搞清楚:求助时你会分享什么

Hermes 有两条分享路径,很容易混在一起:

  • hermes dump —— 打印一份配置摘要,专门设计给人复制到 Discord/GitHub 的。默认只显示密钥“已设置/未设置”,加 --show-keys 会显示密钥的前后各 4 位。
  • hermes debug share —— 打包日志(每个日志文件默认取最后 200 行)并上传到粘贴服务,返回一个链接。

第二条命令的选项值得逐一看清楚:

--lines LINES    每个日志文件包含的行数(默认 200)
--expire EXPIRE  dpaste.com 回退保留天数(最小 1,默认 1)
--local          只在本地打印报告,不上传
-y, --yes        跳过确认直接上传(脚本/CI 里必须显式加,否则拒绝上传)
--no-redact      关闭上传时的密钥脱敏
--nous           上传到 Nous 内部存储(AWS S3),而不是公开粘贴站

上传目的地决定了“万一泄露”的后果截然不同:

目的地 可见性 保留时间 能否删除
paste.rs 公开链接 6 小时后自动删除 到期即删
dpaste.com(回退) 公开链接 由 --expire 决定天数(默认 1 天) 无法删除
--nous(S3) 仅 Nous 内部人员与白名单版主,需 Google 登录才能查看 14 天后自动删除 到期即删

也就是说,日志走公开粘贴站时,你只有 6 小时的窗口;一旦回退到 dpaste,链接会按你给的天数一直挂着,而且删不掉。分享前想清楚这一点,比事后补救有用得多。

问题出在哪:str() 打印了一个带密钥的对象

根因其实很朴素:dump 里有一段代码把备用 provider 列表当普通对象打印:

overrides["fallback_providers"] = str(fallbacks)

str() 会老老实实把每个条目的 api_key 字段,以及 base_url 里可能内嵌的凭证(形如 https://user:token@gateway)一起展开。更麻烦的是第二层:_capture_dump() 返回诊断包时没有做任何脱敏——上传时的脱敏只跑在日志快照上,配置摘要那一段是干干净净“裸奔”出去的。

要判断自己手上的包有没有中招,其实一行 grep 就能自查(注意别把它贴到公开频道里):

hermes dump | grep -n -i "api_key\|base_url\|token\|signature"

如果你看到的是 api_key: sk-... 这种完整值,而不是 ***,那这个包就不能公开。

修好之后:两处强制脱敏 + 三种曾经漏掉的写法

补丁做了三件事,值得逐个理解,因为它们决定了你现在能不能放心粘贴。

第一,dump 在生成时就被脱敏,而不是等到上传。 现在 fallback provider 的条目会先经过一层字段级掩码,再走一次强制文本脱敏,并且开启“URL 凭证”这类严格规则:

overrides["fallback_providers"] = redact_sensitive_text(
    str(_mask_fallback_keys(fallbacks)), force=True, redact_url_credentials=True
)

第二,上传边界自己也会脱敏一遍。 _capture_dump() 现在对诊断包做同样的强制脱敏,并且尊重 --no-redact——也就是说即使某个调用方忘了脱敏,包在出门前还会被拦一次。云存储签名也在掩码范围内:GCS 的 X-Goog-Signature、Azure SAS 的 sig 参数、以及 X-Amz-Signature 现在都算 URL 凭证。

第三,字段级掩码补上了文本脱敏的三个盲区。 这是最容易被忽略、但最实用的一条:单靠文本规则会漏掉三种 api_key 写法——

  • 未加引号的纯数字 key(YAML 里是整数,不是字符串);
  • key 写成了映射(mapping)而不是标量;
  • key 里本身含有 ***,脱敏器认为“已经掩码过了”,于是跳过。

所以现在的顺序是:先按字段名掩码,再把条目转成文本。顺序反过来,就还是会漏。

你自己该怎么用

三条实践建议,都是这套机制落地后才有意义的:

  1. 优先 --nous,而不是公开粘贴站。 需要别人帮你看日志时,用 hermes debug share --nous 上传到需要登录才能查看的私有存储,链接 14 天自动过期;只有在对方拿不到内部访问权时,才退回公开粘贴,并接受 6 小时的窗口。
  2. 不要随手加 --no-redact。 它的语义是“上传前不脱敏”,命令会同时打印一条警告横幅提示日志里的密钥不会被处理。只有当你已经逐行检查过内容、并且确实需要原始字节(比如要排查某个被掩码掉的 token 前缀)时才用它。
  3. 贴 issue 时贴 dump,不要贴 config.yaml。 hermes dump 的输出经过设计,只显示“已设置/未设置”;而配置文件本身包含全部密钥。这个习惯和我们在 会话导出指南 里强调的其实是一回事:导出/分享路径上的脱敏不是可选项,是默认行为,你要做的就是别把它关掉。

顺带一提,--show-keys 也不是“显示密钥”——它显示的是前后各 4 位,用来核对“我用的到底是不是那把 key”,而不是给你复制用的。

顺带核对一下你自己的机器

如果你在 9 月底之前分享过调试包,值得花两分钟做一次检查:

hermes --version                      # 确认版本,v0.21.5 之后的 main 已包含该修复
hermes dump | grep -c "api_key: \*\*\*"   # 期望 > 0,说明字段级掩码生效

如果第一条命令显示的版本较早,hermes update 之后再做一次检查。已经贴出去的公开链接如果落在 dpaste 上,那就只能去对应 provider 后台轮换那把 key——这正是为什么“分享前先看一眼”比任何补丁都便宜。

其他与脱敏相关的机制,比如会话镜像、平台聊天与 bot 聊天各自的出口策略,可以看 脱敏通道那篇;命令本身的完整参数见 hermes dump 与 hermes debug。

总结

  • hermes dump 里的备用 provider 列表曾经把 api_key 和 base_url 内的凭证原样打印,原因是 str() 展开 + 上传边界未脱敏。
  • 现在有两层强制脱敏:dump 生成时(字段级掩码 + 严格 URL 凭证规则)和上传边界(含 --no-redact 开关)。
  • 漏掉的三种写法(纯数字 key、映射 key、含 *** 的 key)现在由字段级掩码兜住。
  • 分享日志优先 --nous(私有、14 天),公开粘贴站只有 6 小时窗口,dpaste 回退无法删除。
  • --no-redact 是“放弃保护”,不是“更详细”;贴 issue 请贴 dump,不要贴配置文件。