· via dev.to (home feed)
Rust Miri wrote CI secrets into target/, and GitHub Actions caches handed them to PRs
A September 2026 Rust advisory reveals cargo miri saved every environment variable into target/, so cached CI directories could hand secrets to later pull request runs.

What happened
On 21 September 2026 the Rust project published a security advisory, summarised in a write-up on dev.to, describing how Miri — the interpreter Rust uses to detect undefined behaviour — leaked CI secrets into GitHub Actions caches. To notice when build-relevant inputs changed between runs, cargo miri recorded the environment it was launched with, and it recorded all of it rather than only the variables it actually needed. That snapshot was written into the Cargo target/ directory, which is exactly what most Rust CI pipelines cache with actions/cache or Swatinem/rust-cache.
The consequence: any secret passed to a Miri step as an environment variable could land on disk inside the cached directory, where a later pull request run could restore it and read it. No CVE was assigned. The nightly toolchain dated 22 September 2026 ends the behaviour; Miri now persists only CARGO_* variables (with CARGO_*_TOKEN excluded) and OUT_DIR.
How the leak worked
According to the dev.to post, the vulnerable pattern needs four things to line up: a workflow that runs cargo miri in CI, a secret injected as an environment variable into that step, the target/ directory cached afterwards, and pull requests allowed to read that cache. No crash, panic or verbose diagnostics are required, and the secret never has to appear in test output or logs — simply passing it to the step was enough for the environment snapshot to place it in target/.
GitHub Actions cache scoping then does the damage. A workflow run can restore caches created on its own branch and on the default branch, so an unprivileged pull request run can pull back a cache written by a privileged push to main. The advisory also notes, per the post, that someone who lifted a secret this way could push further commits to cover their tracks, meaning a clean-looking workflow log is not proof that nothing happened.
Cleanup for affected projects
The remaining work is remediation rather than defence, the dev.to author writes: move to a toolchain from 22 September 2026 or later, delete the caches you already have stored, and rotate anything that was in scope. Caching the Cargo registry or the Miri sysroot was never the issue — target/ is the directory that leaked. GitHub's CLI helps with inspection: gh cache list and gh cache download let you pull a cache locally and check its contents before assuming it is clean.
Hardening steps
The post recommends several changes beyond updating the toolchain:
- Keep production secrets out of Miri runs entirely; if tests genuinely need a credential, use a scoped, revocable dummy value with no real permissions.
- Do not cache target/ on jobs that see secrets — or do not give those jobs secrets in the first place. If you cache anything for Miri, cache the sysroot under a distinct key so it never collides with a standard build cache, and confirm the path with rustc --print sysroot rather than hardcoding it.
- Add a top-level permissions block to workflows, such as contents: read, so a poisoned cache limits what a run can actually do.
- Isolate Miri jobs from secret-bearing jobs, injecting secrets at step level only where they are genuinely needed. The author also corrects a common copy-paste mistake: secrets: inherit is valid only on a job that calls a reusable workflow with uses, not on one running its own steps.
- Audit caches regularly, for example by searching workflow files for actions/cache entries whose paths overlap with directories that secret-injected steps write to.
Why it matters
This is a bug class, not a Rust accident. Any tool that snapshots its invocation environment into a build directory is a candidate for the same leak, and build directories are precisely what CI caching targets. Caches are persistent artefacts that outlive the run which created them and cross trust boundaries that environment variables do not — GitHub's scoping rules will happily hand a pull request a cache built from a protected branch. The Miri advisory is a useful prompt to ask two simple questions of every pipeline: what is actually inside the thing we are caching, and who is allowed to restore it?
- #rust
- #github-actions
- #ci-cd
- #security
- #devops