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.

  1. An outside contributor opens a pull request.
  2. It touches a file the chain executes: a job configuration, a build file, a dependency manifest.
  3. The chain triggers automatically on the pull request.
  4. 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.