ARTICLE / ai
你的 Agent 每跑一条命令,就把环境变量明文抄进磁盘一次
一个看起来无害的功能——终端会话快照——正在把凭据写成明文文件。GitHub 公开 issue(NousResearch/hermes-agent #62336,2026 年 7 月提出,仍处 open)报告:Hermes Agent 的终端环境快照机制用 export -p 把全部环境变量落盘,包括由 Bitwarden Secrets Manager 和启动包装器注入的密钥。这些快照持久在 cache/terminal/hermes-snap-*.sh 里,报告者在检测到之前已经攒出 21 个快照文件、约 2MB 含凭据数据。
报告给出的影响面有四层:凭据跨会话存活(文件不随会话结束消失);任何能读 Hermes home 目录的进程或用户都能读到它们;模型自己也能通过 read_file、search_files 工具调用读到;以及最容易被忽略的一点——这个机制从快照功能引入之日起就一直如此,不是某次回归。
这篇想讲清楚三件事:这类泄漏为什么会在"工程上合理"的设计里发生、社区修到哪一步、以及作为用户你现在能做什么。
快照为什么会存在,又为什么会带毒
Agent 框架需要让多条终端命令之间保持 shell 状态(cd、export 的变量),常见实现是每条命令执行前后做一次环境快照再恢复。Hermes 的实现里有两个写入点:会话初始化时捕获初始环境,之后每条命令执行完再重导一次。两处都用了不加过滤的 export -p——它会把每一个 declare -x 行原样写出,而 shell 并不知道哪些变量名底下是密码。
评论区有人对活网关复现了完整链路,指出关键的过滤器缺口:子进程环境构建时确实有清洗逻辑,但它只剥离两类名字——从 provider/tool 注册表推导出的黑名单项,以及内部约定前缀(形如 AUXILIARY_*_API_KEY、GATEWAY_RELAY_*)。也就是说,只要凭据变量名不在注册表里、也不符合这两个命名模式——比如 SERVICE_PASSWORD、DB_ACCESS_TOKEN、SERVICE_BEARER——就直接穿透到子进程环境,然后被 export -p 抄进快照。对比之下,同项目里的代码执行工具有按名称形状的检测(拦截 KEY/TOKEN/SECRET/PASSWORD/BEARER 等后缀),但终端环境的构建路径没有接这套检测。
这是一个值得记住的结构性教训:基于名单的脱敏永远追不上基于形状的泄漏。你无法穷举所有组织的密钥变量命名习惯,但 *PASSWORD、*_TOKEN 这类后缀形态几乎覆盖全部实战命名。同一个代码库里两套执行路径,一套有形状检测、另一套只有白名单,泄漏就从缝里漏出来。
修复进展:权限收紧了,内容还在
这条 issue 的评论区把两件事分得很清楚,这也是阅读安全 issue 时该有的分层。
已经修掉的是文件可见性:合并的 PR #60199 在两处 export -p 写入点前加了 umask 077,快照从世界可读变成仅属主可读(0600),堵住了"同机其他用户读 /tmp 快照"这条路。同时快照会在环境销毁时删除,暴露窗口被限定在环境存活期间——但存活期间,全套凭据就是磁盘上的明文。
尚未修掉的是内容本身:issue 剩下的诉求是把密钥值从快照内容里滤掉,或者换掉整个机制。两个尝试做内容过滤的 PR(#48443、#56952)都被关闭未合并;截至最近一次更新,main 分支上的 export -p 仍然不带过滤。开放中的 #77193(按值的来源追踪清洗)和 #72652(快照时点清洗)瞄准这一类问题,还没有落地结论。
报告者给的中期方案很务实:写快照前用一条 grep -vE 管道排除已知凭据前缀,且前缀列表做成可配置而不是硬编码。长期方案则指向架构:用管道或文件描述符递送密钥,而不是环境变量。理由一句话就能说透——一个以捕获并持久化环境状态为功能的系统,和环境变量这种凭据载体在根子上不相容。
你现在能做的四件事
不等上游合并补丁,用户侧的动作按性价比排:
- 检查自己的快照目录。 找到 Agent 的缓存目录(本例形态是
cache/terminal/hermes-snap-*.sh),grep -l 'declare -x.*\(PASSWORD\|TOKEN\|SECRET\|KEY\|BEARER\)'扫一遍。命中了就先删文件,再排查哪些变量名会中招。 - 改变密钥的注入姿势。 如果你的工作流靠
bws run --或 shell profile 把密钥 export 进 Agent 进程,等于主动把它们递给export -p。能改成按需读取(配置文件 + 权限位、keychain 命令行调用)就不要常驻环境变量。 - 给 Agent 的文件读取范围设边界。 这个漏洞的组合条件是"明文落盘"+“模型可读任意文件”。限制 Agent 工作目录、不让它的文件工具够到 home 全目录,能把单点故障降级成局部故障。
- 盯住对应的开放 PR。 #77193 与 #72652 合并前,同类产品的用户都值得检查自己用的 Agent 框架有没有同款快照机制——这不是 Hermes 独有的设计缺陷,“为了恢复状态而全量导出环境"是所有带持久 shell 会话的 Agent 框架的共同诱惑。
抽出一条通用审计规则
把本案推广开:**凡是"保存/恢复状态"的功能,先问它保存了什么、恢复给了谁、落在哪个文件系统位置。**会话快照、上下文压缩存档、调试 core dump、CI 的 artifact 上传,都是同一族——它们的存在理由是复制状态,而复制从不尊重"这里有个秘密"的语义。审计清单只需要三问:这个状态副本写到哪、生命周期多长、谁能读到它。三个答案里有一个失控,就值得在凭据进入该进程之前做隔离。
对安全从业者,这条 issue 还有一个样本价值:它展示了负责任的披露—修复节奏应该长什么样。报告给出根因定位(两处行号)、影响分级、中期方案和长期架构方向;维护方区分"已修的可见性"与"未修的内容"两条线各自挂 PR;复现者在评论区贴出对活网关的验证并诚实标注暴露窗口的边界(mode 600、随环境销毁)。对照那些"收到,感谢反馈,关闭"了事的项目,这是可以拿去当模板的交流质量。
本文面向授权安全研究与防御建设,未经授权对任何系统实施文中技术属违法行为。文中事实均来自 GitHub 公开 issue NousResearch/hermes-agent #62336 及其评论区的公开记录(采集于 2026-11-01)。