Cache poisoning is a real class of attack, and GitHub documents it. Pull request runs are scoped so they cannot write caches the default branch restores, and low-trust triggers are restricted. The risk returns if you opt out of that default or restore untrusted caches into privileged jobs.
Definition
A dependency cache stores files, such as downloaded packages, so later runs start faster. Cache poisoning means an attacker writes malicious files into a cache that a later, more trusted workflow restores and runs. GitHub notes cache contents are not signed or verified, and any run that can read a cache may extract its contents.
What GitHub says about scope
- Anyone with read access can open a pull request and read cache contents, and forks can create pull requests on the base branch and access caches on the base branch. So never put tokens or credentials in a cache path.
- A pull request run can restore caches from the base branch, including base branches of forks.
- The pull_request event is not affected by the new restriction. Caches it creates are scoped to the merge ref and cannot be written to the default branch's scope.
- Triggers that can be started by people without write access, such as pull_request_target, issue_comment and workflow_run, are restricted from creating or overwriting caches in the default branch scope. A repository can opt out by declaring a write-capable cache mode.
How an attack would work
- A workflow on a low-trust trigger checks out contributor code and runs a build that saves a cache.
- The attacker's build writes a tampered package or tool into the cache path.
- A later release or deploy workflow restores that cache with the same key.
- The tampered file runs with the release job's secrets.
The default restrictions are designed to stop step 2 from reaching step 3. Opting out removes the guard.
Maintainer checklist
- Search workflows for any declared write-capable cache mode on a low-trust trigger. Remove it unless the job never touches untrusted input.
- Do not restore caches in release, publish or deploy jobs. Rebuild from locked dependencies instead.
- Use lockfiles and verify package integrity, so a poisoned cache cannot silently change versions.
- Keep cache keys specific, including a hash of the lockfile.
- Treat any cache produced by an overridden low-trust job as untrusted in every workflow that restores it.
- Never cache credentials or tokens.
Does it matter for small repositories?
If your release job restores a cache and holds publishing credentials, yes. The cost of rebuilding without a cache is minutes. The cost of a poisoned release is a supply chain incident.
Try it yourself
Click through it at your own pace, or open it full screen.
Look at the dependency inventory, which is the list a lockfile based rebuild should match.
Where Safeguard fits
Safeguard's SBOM and dependency scanning list what actually goes into a build, so unexpected version changes stand out. A free scan shows your own inventory.
Quick check
Can a normal pull_request run write to the default branch cache?
No. GitHub scopes pull_request caches to the merge ref.
Should release jobs restore caches?
It is safer not to. Rebuild from a lockfile so a cache cannot change what ships.
Sources
Last reviewed: 19 October 2026.