Most of what a modern application runs was written by people outside your organisation, and your build system pulls it in and executes it with whatever credentials are lying around. Two public incidents show the range of risk. In 2024, a backdoor was found in the xz compression library (CVE-2024-3094), planted in release artefacts of a widely used open source project. In 2025, the tj-actions/changed-files GitHub Action was compromised (CVE-2025-30066), exposing CI secrets in build logs.
You can't review all this code, so the realistic aim is narrower: know exactly what you ran, limit what a bad component can reach, and be able to prove what you shipped. Four habits do most of that work.
Pin what you build with
A version range means that the same commit can produce different software on different days. Pinning makes builds repeatable and makes upstream changes visible as a reviewable diff.
- Commit lockfiles, and use the install modes that honour them, such as
npm ci. For Python, considerpip install --require-hashes. - Reference container base images by digest, as tags can be moved.
- Pin CI actions and reusable workflows to a full commit SHA. GitHub's security guidance identifies this as the way to use an action as an immutable release, since tags can be repointed, which is what happened with
tj-actions. - Install from an internal proxy or registry if you can, so that a deleted upstream package doesn't break your build and you have one place to apply policy.
Pinning has a cost. A pin that never moves goes stale and collects known vulnerabilities, so pair it with automated update pull requests and a test suite you trust. Consider letting new releases age a few days before they are adopted automatically, which gives the community time to spot a malicious version. Apply security fixes faster than that, on their own track.
Generate an SBOM and use it
A software bill of materials is a formal, machine-readable inventory of the components in a piece of software, including versions and supply relationships. CycloneDX and SPDX are the two common formats.
An SBOM won't stop anything by itself. Its value appears the morning a new critical vulnerability is announced, when the question is "where do we run this?" and you can answer with a query instead of a week of emails.
- Generate one in the pipeline for every release, from the built artefact where possible, so it reflects what actually shipped.
- Store it alongside the artefact, keyed by version or digest.
- Load SBOMs into something searchable, and feed them to vulnerability matching so you find out without having to ask.
- Ask key software suppliers for theirs as well. You inherit their dependencies.
Treat CI as production
Your build system can usually publish software and deploy it to production. Attackers know that, so its credentials deserve at least as much care as any production administrator's.
- Replace long-lived cloud keys with short-lived tokens using OIDC federation between your CI platform and your cloud provider.
- Limit each token to one repository and, ideally, one protected branch or environment.
- Set default CI token permissions to read-only, and grant more per job.
- Don't expose secrets to workflows triggered by pull requests from forks.
- Split build and deploy into separate jobs with separate credentials, so a compromised test dependency can't reach the production deploy key.
- Prefer ephemeral runners over shared long-lived ones, so that a compromised job doesn't leave anything behind for the next.
Sign artefacts and check the signature
Signing lets you show that an artefact came from your pipeline and hasn't changed since. Sigstore has made this far easier, with tools such as Cosign and keyless signing tied to the CI workload's identity. Provenance records go further by describing how and from what source the artefact was built, and the SLSA framework offers useful levels to work towards.
The step many teams skip is verification. A signature nobody checks is decoration. Put the check in the deployment path, for example with an admission policy in Kubernetes, and have it reject unsigned images. Start in audit mode to find the exceptions, then enforce.
What to do this week
- Search your workflows for actions referenced by tag or branch rather than SHA, and pin the ones with access to secrets first.
- List every long-lived credential stored in your CI platform, noting what each one can reach. Replace the most powerful with short-lived federation.
- Add SBOM generation to one production pipeline.
- Rehearse the question: pick a library at random and time how long it takes to find every service that uses it.
That last number is the honest measure of where you stand.