← 文章 / 云原生与基础设施
rust-lang 4小时前 · 2026-09-25 04:38:01 · 1 阅读

当 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 完成。

  1. 由于复杂的原因,cargo miri 会多次调用 Miri ↩

  2. 理论上,构建脚本也可能从网络读取数据 ↩

原始来源: rust-lang

评论 (0)