Securing the build chain
CI/CD integration explains how to produce an SBOM in the build chain. This page addresses the reverse problem: the chain itself.
Why this is a compliance matter
The build chain produces the artefact, generates the SBOM, applies the signature and issues the provenance attestation. If it is compromised, everything downstream loses its value: the SBOM describes whatever the attacker chooses to show, and the signature authenticates a tampered binary. SolarWinds and Codecov are exactly that.
The Regulation devotes no article to the build chain, but three of its requirements lead there: limiting attack surfaces and protecting integrity (Annex I, Part I), secure distribution of updates (Annex I, Part II, point 7), and due diligence on third-party components (Art. 13(5)) — which also covers the components the chain itself consumes, actions and images included.
The six ways code executes in a pipeline
The counter-intuitive point: most of them exploit no vulnerability. They are documented features, used as designed.
| Mechanism | How it works | Typical example |
|---|---|---|
| Direct execution | The tool’s purpose is to run commands described in a repository file | A build file launching a shell script |
| Executable configuration | The config file is not parsed, it is evaluated | A tool configuration written in JavaScript or Python |
| Lifecycle scripts | The package manager triggers scripts at install time | A package’s install hooks |
| Registry redirection | A repository file redirects resolution to an attacker-controlled source | A committed registry configuration file |
| Environment poisoning | A variable silently changes a tool’s behaviour | A variable read by the shell or by an archive utility |
| Booby-trapped input file | A malformed archive or file exploits the parser handling it | An archive with absolute paths, a forged project file |
The practical consequence: disabling install scripts is not enough. A repository that can modify any configuration file can, in most ecosystems, obtain code execution.
The pull-request attack
This is the dominant scenario, and it requires no access to the repository.
- An outside contributor opens a pull request.
- It touches a file the chain executes: a job configuration, a build file, a dependency manifest.
- The chain triggers automatically on the pull request.
- If it runs with the repository’s secrets, they are exfiltrated — often quietly, through an outbound request or by writing them into the logs.
The design error that makes this possible is a single one: executing unreviewed code in a context that holds secrets.
Countermeasures, by effectiveness
1. Separate the execution contexts
The structural measure, and the only one that addresses the cause.
| Context | Trigger | Secrets | What it does |
|---|---|---|---|
| Untrusted | External pull request | None | Build, test, static analysis |
| Trusted | After merge, or on explicit approval | Yes | Publish, sign, deploy |
Anything needing a secret belongs to the second. No unmerged code runs there.
2. Pin by hash, never by tag
A tag is mutable: it can be repointed to another commit with nothing changing on your side. That is what happened to a widely used CI action in March 2025. Actions, base images and remote scripts are referenced by hash.
See Locking and updating dependencies.
3. Protect the files that execute
Chain definition files, dependency manifests and evaluated configurations deserve their own review regime: designated owners, mandatory approval, and an alert on modification.
4. Least privilege on tokens
By default a chain token should be read-only. Write, publish and identity-issuing permissions are granted job by job, and only where they are used. An ephemeral token beats a long-lived secret.
5. Disable what is not needed
Install without running lifecycle scripts, configuration paths forced on the command line rather than read from the repository, registries forced by argument.
6. Isolate the runners
Ephemeral runners, destroyed after each job, with no uncontrolled outbound network access. Egress filtering turns silent exfiltration into a visible failure.
7. Analyse the chain as code
Three families of open tools inspect chain definitions and detect these defects:
| Tool | What it looks at |
|---|---|
| actionlint | Job definition syntax and the safety of embedded scripts |
| zizmor | Security defects specific to hosted build chains |
| poutine | Supply chain analysis of build chains |
They run in the chain, on the chain, and their failure must block just as a test does.
What this produces as evidence
| Artefact | What it is for |
|---|---|
| Versioned chain configuration | Shows the exact state on a date, for a shipped version |
| Dated chain analysis reports | Evidence of the regular testing required by Annex I, Part II, point 3 |
| Provenance attestation signed by the build service | Ties the artefact to the actual revision and parameters |
| Waiver log, with expiry | Shows blocks are controlled, not bypassed |
| Documented token policy | Evidence of least privilege |
How this relates to the provenance level you target
Separating contexts, isolating runners and preventing user-defined steps from reaching the signing material are exactly the conditions of the higher levels of the SLSA framework — see Signing and integrity. Securing the chain is therefore not a separate project: it is what makes the attestation credible.