Build1 publisherNot yet confirmed elsewhere2 min readPublished
Rust's Miri fix leaves CI secrets sitting in GitHub Actions caches built before it
Rust's nightly of 22 September 2026 stops Miri copying environment variables into the target/ directories CI jobs cache. Caches saved earlier still hold any secret a Miri step saw, so teams have to delete them and rotate those credentials.
The Engineer · Build desk

What happened
- To spot build-relevant changes between runs, cargo miri recorded its entire invocation environment in a file inside target/.
- The advisory says a project is exposed if it runs cargo miri with secrets in the environment, caches target/ via actions/cache or Swatinem/rust-cache, and lets pull requests read it.
- According to the advisory, an attacker who took a secret this way could make later commits to hide the theft. A clean workflow log therefore proves nothing.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- decision Upgrading Miri only protects future runs, so every affected repository needs cache deletion scheduled as its own task, separate from the toolchain bump.
- exposure Every credential passed to a Miri step while target/ was cached has to be treated as read, because neither deleting caches nor reviewing logs can show it was not.
- constraint With no CVE assigned, teams that triage from CVE feeds will not be alerted and have to pick the advisory up from the Rust project itself.
- precedent Any other tool that writes its invocation environment into a cached build directory can leak the same way, so secret-bearing jobs that cache build output need the same review.
The Rust project published its advisory on 21 September 2026 [1]. A dev.to write-up of that advisory describes the fix as an allowlist. Starting with the nightly dated 22 September 2026, Miri writes out only CARGO_* variables, minus CARGO_*_TOKEN, plus OUT_DIR [2]. The exclusion is the careful part. Token variables also start with CARGO_, so a plain prefix rule would have kept the token variables in Cargo's own namespace [15].
The allowlist changes what Miri writes on its next run. It does nothing to cache entries that are already stored, so the write-up lists cache deletion as a separate step after the toolchain update [8]. The vulnerable example in the write-up shows why the upgrade on its own leaves those entries in use. It caches `target` under `key: miri-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}` [14]. That key is built from the runner OS and the lockfile hash. Moving to the fixed nightly changes neither, so the first run after the upgrade asks for the same key the old Miri filled [16]. A workflow still pinned to a nightly from before 22 September keeps writing out the full environment [17].
Deleting a cache removes the stored copy. It does not touch a copy that a pull request run has already restored, and under GitHub's scoping rules a pull request run could restore caches built on the default branch [5]. If a secret was in one of those caches, the safe assumption is that someone read it. Rotation is the write-up's third step for that reason [18].
The scope is narrower than every cache in CI. Registry and Miri sysroot caches were never the problem. The target/ directory was [10]. The secret did not have to show up in code, test output or logs to be written to disk, so searching old logs will not find the exposure [9]. What needs rotating is every credential a cargo miri step got as an environment variable while that job's target/ was being cached.
For new runs, the write-up recommends never giving Miri production secrets. If a test really needs a credential, it suggests a dummy value or a scoped, revocable test credential with no real permissions [11]. Its other recommendation is to stop caching target/ on any job that can see secrets [12].
What to watch
- Whether a CVE is later assigned, which would bring the Miri advisory into CVE-driven scanners and compliance trackers.
- Whether other cargo subcommands or build tools turn out to snapshot their environment into target/ the way cargo miri did.
- Whether Swatinem/rust-cache or common Rust CI templates change their defaults for caching target/ on jobs that receive secrets.
Clarity's read
What the record supports and how the coverage leans. The claims behind it follow.
Reality
- Evidence50
- Adoption
- Insufficient
- Hype gap+5
- Incentives
- Insufficient
- Confidence55
Claim ledger
Ranked by verification strength, evidence, and original report placement.
- [1]
The Rust project published an advisory on 21 September 2026 reporting that Miri, Rust's interpreter for detecting undefined behavior, was writing every environment variable it saw into the target/ directory.
- [2]
The nightly toolchain dated 22 September 2026 stops Miri persisting arbitrary environment variables, keeping only CARGO_* (excluding CARGO_*_TOKEN) and OUT_DIR.
- [3]
No CVE was assigned to the Miri advisory.
- [4]
To notice when something build-relevant changed between runs, cargo miri recorded the environment it was invoked with, all of it rather than only the variables it needed, and that record lived inside the target/ directory.
- [5]
A GitHub Actions workflow run can restore caches created on its own branch and on the default branch, so a pull request run can restore a cache that a privileged push to main created.
- [6]
According to the advisory, a project is affected if it runs cargo miri in CI, passes secrets as environment variables to that step, caches the target/ directory with something like actions/cache or Swatinem/rust-cache, and allows pull requests to read from that cache.
- [7]
The advisory notes that someone who lifted a secret this way could push further commits to cover their tracks, so a clean-looking workflow log is not evidence a project was unaffected.
- [8]
The remaining work for affected teams is cleanup: update the toolchain, delete the caches already created, and rotate anything that was in scope.
- [9]
Nothing in a project's code, test output or logs had to mention the secret; passing it to the Miri step was enough for it to land on disk in target/.
- [10]
The target/ directory is the part that leaked; caching the Cargo registry or the Miri sysroot was never the problem.
- [11]
The write-up recommends never injecting production secrets into Miri test runs, and using a dummy value or a scoped, revocable test credential with no real permissions if tests need one.
- [12]
The write-up recommends fixing caching scope, for example by not caching target/ on jobs that see secrets.
- [13]
Any tool that snapshots its invocation environment into a build directory is a candidate for this class of bug, and build directories are what CI caching is pointed at.
- [14]
The write-up's vulnerable example caches path target with actions/cache@v4 under the key miri-${{ runner.os }}-${{ hashFiles('**/Cargo.lock') }}.
- [15]
CARGO_*_TOKEN variables match the CARGO_* prefix, so without the explicit exclusion the allowlist would have kept token variables.
- [16]
The example cache key is built only from the runner OS and the Cargo.lock hash, so moving to the fixed nightly does not change the key and the post-upgrade run requests the same cache entry the old Miri populated.
- [17]
Any nightly dated before 22 September 2026 predates the fix and still persists the full environment into target/.
- [18]
Deleting a cache removes the stored copy but cannot undo a restore a pull request run already performed, and logs cannot show a secret was not taken, so credentials that were in scope must be rotated as if read.
Sources
1 independent publisher whose own reporting we read for this story.
- dev.toMiri Was Leaking CI Secrets Through GitHub Actions Caches: What to Clean Up
1 article · October 7, 2026
Topics and entities
Follow any of these and your For You feed starts watching them — no settings page required.