Prevent GitHub Actions cache poisoning
Keep untrusted code from writing cache entries that trusted release or deploy jobs restore, and cache inert dependency data rather than executable workspace state or credentials.
How StarSling worksAI agents open the PR
StarSling agents inspect your workflow, apply this optimization, and open a reviewable PR automatically.
Do this: keep release jobs off shared caches
Pull request jobs can restore approved dependency caches but cannot create trusted release state. Release and publish jobs either install from verified lockfiles without a shared cache or use a separate cache namespace writable only by trusted triggers. Cache paths exclude build outputs, hooks, credential files, and anything later executed without verification.12
on:
push:
tags:
- "v*"
permissions:
contents: read
jobs:
release:
runs-on: ubuntu-latest
environment: release
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v6
with:
node-version: 22
- run: npm ci --ignore-scripts
- run: npm run build
- run: npm testAvoid this: restore untrusted state before publishing
An attacker who influences the shared workspace can replace executable output that a later privileged job trusts and publishes.
on:
workflow_dispatch:
inputs:
head_repository:
required: true
type: string
head_sha:
required: true
type: string
push:
tags:
- "v*"
jobs:
cache-untrusted-head:
if: github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
# workflow_dispatch can write caches, but these inputs can select a fork.
- uses: actions/checkout@v7
with:
repository: ${{ inputs.head_repository }}
ref: ${{ inputs.head_sha }}
- uses: actions/cache@v4
with:
path: .
key: shared-workspace
release:
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/cache@v4
with:
path: .
key: shared-workspace
- run: ./dist/publishTreat platform restrictions as a layer, not the design
GitHub.com now gives low-trust triggers read-only access to default-branch cache scope, including pull_request_target, issue_comment, and workflow_run; a save attempt from those triggers fails. The architectural rule still matters for GitHub Enterprise Server, third-party cache backends, trusted triggers that fetch untrusted content, and any cache containing executable state. The ci-secure skill reports candidate cache/trust-boundary patterns with this platform mitigation so you can distinguish a blocked GitHub.com path from a reachable residual.
Runs on your machine. GitHub API optional, for the impostor-SHA check and the dormancy note.
Why it matters
Caches outlive one job and are restored implicitly by key and scope. If an untrusted run can write executable or credential-bearing state that a trusted job restores, the attacker crosses from an unprivileged contribution into the release path without modifying the protected branch.
When a cache crosses trust boundaries
Use it when
Review any cache restored by a release, deploy, publish, signing, or other secret-bearing job, especially when another trigger or cache backend can write the same namespace.
Be careful when
Do not cache whole workspaces or release outputs merely to save a short build. If a trusted job cannot validate the cache contents before executing them, rebuild instead.
Verify on your repo
Hand this prompt to your coding agent (Claude Code, Cursor, and the like) to audit and fix this practice in your own repo.
Audit GitHub Actions caches across trust boundaries. Map each cache key, scope, backend, writer, and restorer. Identify any cache an untrusted or attacker-influenceable workflow can write that a release, deploy, publish, or secret-bearing job later restores, plus cache paths containing whole workspaces, executable outputs, or credential files. Remove caches from privileged release paths when possible; otherwise create a trusted-only namespace and cache only inert dependency data. Preserve build reproducibility, show the diff, and open a PR rather than applying it blindly.
Ground these changes in the upstream docs before you edit: https://docs.github.com/en/actions/reference/workflows-and-actions/dependency-caching, https://securitylab.github.com/resources/github-actions-preventing-pwn-requests/. If you cannot fetch them, say so rather than guessing, and cite what you used in the PR description.Prefer to check by hand?
Map every cache key and backend to all workflows that can write or restore it, including reusable workflows and non-GitHub cache services.
Flag any namespace writable by an untrusted or attacker-influenceable run and restored by a privileged job.
Remove shared cache restores from release jobs or isolate trusted writers; narrow cache paths to inert dependency stores and exclude credentials and executable outputs.
Go further
One fix, all of them, or forever.
You have the prompt for this one practice. Here is how much further you can take it, each step doing more for you than the last.
Fix this one thing
Copy the prompt above
Hand P14.7 / P8.3 to your coding agent and fix it in your repo today.
Fix everything, once
Install the ci-secure skill
One prompt checks every workflow against ten critical exploit-chain attack vectors, explains each reachable path, and lets you choose focused fixes. Open source, runs locally. It is not a comprehensive security audit.
Keep it fixed, forever
Install the StarSling GitHub App
Connect GitHub and the fixes stay applied as your CI evolves, with agents that keep inspecting your workflows and opening optimization PRs you review.
More best practices for GitHub Actions
Where to go next in the CI best-practices catalog.
- Security
Secure pull_request_target in GitHub Actions
Use
pull_requestfor any job that checks out, builds, installs, or tests contributor code; reservepull_request_targetfor metadata-only work on trusted base-branch code with minimal permissions. - Security
GitHub Actions secrets: prevent leaks and overexposure
Expose a GitHub Actions secret only to the step that consumes it, gate production secrets with an environment, prefer short-lived OIDC credentials, and keep credentials out of logs, command arguments, caches, and artifacts.
- Caching & Setup
GitHub Actions cache: dependencies, keys, and cache hits
The GitHub Actions cache reuses package-manager downloads between workflow runs.
Trying to make GitHub Actions faster?
If you do not know why a run is slow yet, start with the diagnostic guide. Let an agent find which of these applies: /ci-speedup. If your workflows are already optimized but still slow, see our guide to fast GitHub Actions and GitHub Actions runner alternatives. Building containers in CI? The Docker workflow guide covers layer caching end to end. Want to see how your configuration measures up before changing anything? CI Score grades workflow config against a pass/fail rubric of these practices. It is not a speed measurement.
FAQ
What is GitHub Actions cache poisoning?
It is a trust-boundary attack where an attacker influences a cache entry that a more privileged workflow later restores and executes or otherwise trusts.
Does GitHub block cache poisoning automatically?
GitHub.com restricts writes from low-trust triggers to default-branch cache scope, which closes important paths. It does not cover every cache backend, Enterprise Server version, trusted trigger that runs untrusted code, or unsafe executable content, so workflows still need separation.
Sources
1GitHub Actions · dependency caching reference (opens in new tab)
2GitHub Security Lab · preventing pwn requests (opens in new tab)
Last updated 2026-08-19