当 Miri 输出被缓存时 GitHub Actions 泄露密钥
Rust 安全响应团队收到通知,Miri 会将所有环境变量写入 target/ 目录,导致密钥可能通过缓存持久化保留。
这本身不一定构成漏洞,但结合 GitHub Actions 的缓存机制,可能会导致密钥在 PR 中暴露。
概述
GitHub Actions 允许在不同运行之间缓存目录。常见的配置是让 main(及其他分支)的 CI 运行写入缓存,而 PR 只能从缓存中读取(以防止缓存投毒)。Rust 项目通常会缓存 cargo install 构建的二进制文件,有时也会缓存 target/ 目录内容,以加快 CI 速度。
PR CI 可以被任何能向你仓库提交 PR 的人触发。GitHub 要求维护者批准第一个 PR,但后续的 PR 在每次推送时都会重新运行 CI。任何曾经合并过代码的人都可以通过触发 CI 运行,从缓存的 target/ 中提取信息,然后通过向该 PR 推送第二个提交来掩盖痕迹。
GitHub 有时会在界面中隐藏被覆盖的提交,这使得此类攻击更难检测。CI 运行日志和被覆盖的提交也会在几个月后被删除。
当调用 cargo miri 时,Miri 需要在运行之间保留与构建相关的环境变量1。目前的代码通过将所有环境变量存储到 target/来实现这一点。当然,当 target/ 被缓存时,这些变量也会随之持久化。
如果环境中包含密钥,现在 PR 可以通过缓存访问这些密钥。
我们的修复方案
我们针对此问题的短期修复是让 Miri 仅保留 CARGO_* 环境变量(不包括 CARGO_*_TOKEN)和 OUT_DIR。长期来看,Miri 和 cargo 可能会找到更好的方式来告知 Miri 相关的环境变量列表。请注意,该补丁可能尚未在 nightly 版本中提供。
我们还对 GitHub 上的生态系统进行了扫描,发现 1 个仓库存在此问题,另有 7 个仓库看似不受影响但仍需谨慎对待。我们已联系这些维护者。
我是否受到影响?
我们的扫描可能并不全面,因此如果你在 CI 中运行 Miri,建议检查一下自己的 GitHub Actions 配置。
满足以下条件就存在风险:
- 在 CI 中运行
cargo miri - 运行
cargo miri的步骤能访问 secrets(作为环境变量),途径包括:- 直接作为环境变量传给该步骤
- 在 workflow 级别的
env中设置 - 先传给前面的某个步骤,再以某种方式保留在环境中
- workflow 会缓存
target目录,通常是通过actions/cache或swatinem/rust-cache - PR 可以访问该缓存(这很常见,而且往往正是预期用法)
几个可快速采取的修复措施:
- 对该 job 禁用缓存。
- 把 secrets 的范围限制在该 job 中不调用 Miri 的步骤。
- 暂时停用 Miri。
修复后,请清空缓存。如果 secrets 可能已经泄露,建议轮换。
即将发布的 nightly(2026-09-22)中的 Miri 将不再存在此问题。
即使你不运行 Miri,也请确保能写入公共缓存的 job 无法访问 secrets。很多工具并不会对 secrets 做特殊处理,而是默认整个环境都可以写入文件系统。
威胁模型
我们认为,让缓存轻易被 secrets 污染是一种不良实践。
如果你在缓存 target/,最好确保创建 target/ 的进程(即所有调用 cargo 的命令)的输入中不包含 secrets。标准的 cargo build/test 子命令一般很少需要 secrets 或 token2,所以关键在于不要把 secrets 作为环境变量暴露给整个 job。
Cargo/Miri/Rust 并不保证环境变量不会被复制到 target/ 中。尽管我们将其视为安全问题,出于谨慎原则进行了修复,但通常情况下不应依赖此特性。除了官方 Rust 工具链,构建脚本也可能执行其他操作,导致环境变量被存储在编译产物中。
致谢
感谢 OpenAI 的 Predrag Gruevski 向我们报告此问题。此外,生态系统扫描是通过使用 OpenAI 捐赠的 Codex 访问权限和额度完成的,我们也对此表示感谢。
问题的分诊与修复由 Manish Goregaokar、Ralf Jung、Ben Kimock、Weihang Lo、Jacob Finkelman、Walter Pearce、Josh Stone 和 Mark Rousskov 完成。