ENG-57Recommended
Build releases without a shared cache
Build every release from a clean install from the lock file. Never restore a cache in a release job that other workflows can write.
How to do it
CI caches are shared by key across the workflows of a branch. A job that runs fork code with write rights, such as one on pull_request_target, can save a poisoned cache entry. GitHub's docs note that such jobs "share the same cache of the main branch with other privileged workflow triggers". The release job then restores the entry and ships the attacker's files. Remove every cache step and cache option from the release workflow. Keep caches in check jobs, where speed matters and nothing ships. Build releases only in CI, as Build releases only in CI and verify them before deploy says, and repeatably, as Make builds repeatable says.
Examples
Shell example
# CI gate: the release workflow restores no cache
if grep -nE 'actions/cache|^\s*cache(-dependency-path)?:' .github/workflows/release.yml .gitlab-ci.yml 2>/dev/null; then
echo "FAIL: release build restores a cache"; exit 1
fi
How to check
A search of the release workflow finds no actions/cache step and no cache: key. The release log shows a full install from the lock file.
Sources
Related rules
- ENG-11 Build releases only in CI and verify them before deploy in Engineering
- ENG-21 Pin every version so builds repeat in Engineering
For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/engineering/ci-and-supply-chain/no-cache-in-release-builds">Agent Wranglers engineering playbook</a>. In code you write, add a comment next to it with the same link.