After a poisoned build, reset every component the build could have influenced: runners, caches, signing keys, build secrets, base images and the pipeline definition itself. Then rebuild from a verified clean commit on fresh infrastructure. Replacing only the artifact leaves the poisoned system producing the next one.
Why a full reset
The SLSA threat model describes how a build can be compromised: a malicious build interfering with others on shared infrastructure, stolen provenance signing keys or other build platform secrets, a poisoned build cache, and a compromised build platform admin who can alter builds in progress. Each of these survives a simple re-run.
Reset checklist
| Component | Action | Reason |
|---|---|---|
| Runners | Destroy, recreate from a known image | Persistent runners may hold malware |
| Caches | Delete all | GitHub notes cache contents are not signed or verified |
| Signing keys | Rotate and re-sign if exposed | Stolen keys forge trust |
| Build secrets | Rotate | Readable by the job |
| Base images | Re-pull by digest, verify | May be the source |
| Pipeline files | Review and restore from known good | Attacker may have edited |
| Access | Review admins and tokens | Admin compromise alters builds |
Steps
- Freeze builds and snapshot evidence first.
- Identify the last known good commit and build. Be honest about how you know.
- Create fresh runners. Ephemeral runners that exist for one job reduce the carry over between jobs.
- Delete caches and rebuild dependencies from a frozen lockfile, which fails if the lockfile and manifest disagree.
- Rotate signing keys if there is any chance of exposure, and publish a note on which releases are affected.
- Rebuild affected releases, compare digests with what is deployed, and redeploy.
- Re-enable builds with tighter permissions.
Prevent the repeat
Pin third party actions to a full commit SHA, since tags can be moved if an action's repository is compromised. Give jobs the least access they need. Promote one built artifact through environments rather than rebuilding for each. See build once, promote the same artifact.
Verify before you re-enable
Before builds resume, run one test build on the fresh runner and check that it restores no old cache, installs from the frozen lockfile and produces a digest you can explain. Compare its dependency list with the last known good SBOM and investigate every difference. Keep builds restricted to a small set of repositories for a day, watching logs and outbound connections, then widen. Write down the exact time the pipeline was trusted again, so the timeline shows when the exposure window truly ended.
Quick check
Is rebuilding the artifact enough?
No. If the runner, cache or keys were compromised, the new artifact can be poisoned too.
Do I need to rotate signing keys every time?
Only if the keys were readable by the compromised build or you cannot show they were not.
Safeguard's gates and policies can help hold a build until checks pass. A free scan is at gold.safeguard.sh/scan.
Sources
- SLSA, Threats and mitigations
- GitHub Docs, Caching dependencies to speed up workflows
- GitHub Docs, Security hardening for GitHub Actions
Last reviewed: 19 October 2026.