Why CI
Why supply chain security belongs in your CI
The software you ship is whatever your CI build produced, from whatever it downloaded at that moment. That build is the one place where you can see what went in, decide what is allowed in, and prove what came out.
What software supply chain security means
The talk's working definition: your software is your own code plus everything it depends on, and the supply chain is every step that gets those parts into a release.
Petteri Pulkkinen and Erika Marttinen at Kubernetes Community Days Helsinki 2026, 2:35 to 4:28 of SBOMbastic: Getting Ready for Upcoming EU Cybersecurity Regulation in Software Supply Chains.
Your production build is not the build on your laptop
Most teams assume that the code they tested locally is the code that ships. The source is the same, but the software often is not. A build is source code plus everything it fetches and everything around it at the time it runs, and very little of that is fixed.
Unpinned dependencies resolve differently
Dependency ranges are the norm. In npm, ^1.0.4 accepts any later 1.x release, so the same
package.json installs different versions on different days. A lockfile pins them, but only if one is
committed and the build honors it: npm ci fails when the lockfile and package.json
disagree, while npm install can quietly update the lockfile. Plenty of projects do not commit one at all.
Express 5.2.1, a widely used web framework, does not.
Even a pinned version is a name, not a file. pip's documentation explains that version pinning alone does not stop a package index from serving different files for the same version, which is why it offers hash-checking mode.
Time changes the inputs
Registries change between builds. New versions are published, old ones are yanked, and a range that resolved safely
on Monday can pick up a compromised release on Tuesday. Container tags move too: Docker's own documentation warns that
"image tags are mutable, meaning a publisher can update a tag to point to a new image," so
FROM node:20 is not the same base image from one month to the next unless you pin it by digest.
Time also ends up inside the output. The Reproducible Builds project calls timestamps "the biggest source of reproducibility issues," because so many tools record the current date and time into what they build.
The environment is different
- Operating system and architecture. A laptop on macOS and ARM and a CI runner on Linux and x86-64 install different prebuilt binaries for the same package. Many npm tools now ship as platform-specific packages that wrap a compiled binary.
- Toolchains. Compiler, runtime and package manager versions drift between machines unless every one of them is pinned.
- Caches. Your machine and your CI restore different caches, built at different times from different resolutions.
- Install scripts and build steps. Post-install hooks, setup actions and build scripts download tools and data at build time. None of that appears in a manifest.
- Configuration. Environment variables, build flags and secrets exist in CI and nowhere else.
The Reproducible Builds project defines a reproducible build as one where "given the same source code, build environment and build instructions, any party can recreate bit-by-bit identical copies of all specified artifacts." That definition shows how much has to be held constant. Most teams' builds do not get there, and the ones that do worked hard for it. Until you are there, the only reliable account of what shipped is the one recorded while the build that shipped was running.
A software supply chain, walked end to end
Petteri Pulkkinen and Erika Marttinen at Kubernetes Community Days Helsinki 2026, 15:20 to 16:38 of SBOMbastic: Getting Ready for Upcoming EU Cybersecurity Regulation in Software Supply Chains.
Where you can check, and what each place can see
Security checks can run in four places. Each has a job, but only one of them sees the build that actually produced your release.
| Capability | In the CI build | Developer machine | Repository scan | Artifact scan |
|---|---|---|---|---|
| Sees the versions production actually got | Recorded as they are fetched | A different install | Only if a lockfile pins them | What it can recognize |
| Sees transitive and cache-restored packages | Its own cache, not CI's | Lockfile only | ||
| Sees what install scripts download | ||||
| Can stop a malicious outbound call | Egress policy on the runner | |||
| Can prove where the artifact came from | Signed provenance | |||
| Catches problems before code is pushed | On pull requests |
- Included
- Partly
- Not included
A scan of the repository reports what the project declares. A scan of the finished artifact reports what it can recognize inside it. Neither saw the build happen. Express 5.2.1 commits no lockfile, so a scanner pointed at a clean checkout has none of the project's npm dependencies to resolve and reports whatever else it recognizes. Point the same tool at an installed copy of the same repository and one cataloger flag changes the answer again. What comes back depends on what had been installed before the scan, which is exactly the part a scan does not control. The build-time SBOM page covers why scans disagree, and what recording the build changes.
The pipeline is a target in its own right
CI is where source code, dependencies and secrets meet, which makes it worth attacking.
- tj-actions/changed-files (2025). A widely used GitHub Action was compromised and printed CI secrets into build logs.
- Codecov Bash Uploader (2021). A modified script, run in customers' CI, sent environment variables to an attacker's server.
- The Shai-Hulud npm worm (2025). Compromised packages stole credentials on install and used them to publish more compromised packages.
- xz utils (2024). A backdoor was hidden in release tarballs and in the build process itself, not in the visible source.
Each of these ran code during a build or an install. A repository scan run before them, or an artifact scan run after them, would not have seen what the build process did. Controls that watch and limit what a job can reach have to sit where the job runs.
More code, more dependencies, fewer people reading them
AI coding agents write and change code faster than teams can review every line, and they add dependencies along the way. They also name packages that do not exist: a USENIX Security 2025 study of 576,000 generated code samples found hallucinated package rates of at least 5.2% for commercial models and 21.7% for open source models. An attacker who registers one of those names gets installed by the next build that trusts it.
Every change, whoever or whatever wrote it, passes through the build on its way to production. That makes the build the one checkpoint that does not depend on someone having looked.
Evidence has to describe what shipped
Regulations and customers are asking for proof, not intent. The EU Cyber Resilience Act asks manufacturers to identify and document the components in their products and to handle vulnerabilities in them for the product's support period. An SBOM generated from a manifest describes what the project asked for. An SBOM recorded from the build describes what went into the release you are responsible for. When a new vulnerability is published, the second one tells you which releases are affected. Read what the CRA requires.
What "in CI" should mean
Running a scanner as a CI step is a start, but it still looks at files after they arrived. In CI, done properly, means:
- Record, do not infer. Capture every package and download the job actually fetches, including transitive dependencies and anything restored from a cache.
- Say how complete the record is. An SBOM should state whether it covers everything the job fetched, not just list what it found.
- Control egress. Decide which hosts a job may reach, and stop the rest before a compromised dependency can call home.
- Sign what comes out. Link each artifact to the build that produced it, so the SBOM and the thing you deploy are provably the same.
- Keep watching after release. Re-check what shipped as new vulnerabilities appear, and turn findings into fixes.
This is what CRACI does. It is a runner for your GitHub Actions jobs, so it records what each build pulls in, applies
the egress policy, signs provenance and monitors what you shipped. Moving a workflow is one line:
runs-on: craci. See how CRACI works.
What CI does not replace
Putting supply chain security in the build does not make other layers optional.
- Developer machines still hold credentials and run install scripts. Protect them too; a compromised laptop can push code before any build runs.
- Code review and static analysis find flaws in your own code, which a record of dependencies does not.
- Pinning, lockfiles and digests are still good practice. They narrow the gap between builds; recording the build tells you what happened anyway.
The build is not the only place to check. It is the only place that sees the software you actually ship.
See CRACI on your own pipeline
Book a demo and we will walk through your builds, your SBOMs and your compliance evidence.
Book a demo