Continuous integration security is the practice of treating your CI pipeline as a system with the same blast radius as production, because in a meaningful sense it is production: whatever runs in your CI pipeline has the ability to build, sign, and often directly deploy the code that reaches your customers. An attacker who compromises a CI runner doesn't need to find a vulnerability in your application at all, they can just have the pipeline build and ship whatever they want.
This isn't hypothetical. The 2020 SolarWinds compromise involved attackers inserting malicious code during the build process itself, and the pattern of attacking the pipeline rather than the shipped application has only grown more common since, because CI systems are frequently under-scrutinized relative to the production systems they build.
Are your secrets actually scoped and short-lived?
Long-lived, broadly-scoped credentials stored as CI environment variables are the single most common finding in CI security reviews. A cloud deployment credential that's valid indefinitely and scoped to full account access, sitting as a plaintext environment variable available to every job in a pipeline, is a massive amount of risk concentrated in one place. Prefer short-lived, workload-identity-based credentials (OIDC federation to your cloud provider, for instance) that are minted per-job and expire quickly, scoped narrowly to only what that specific job needs.
Audit which jobs and which branches can access which secrets. A secret needed only for deploying from your main branch shouldn't be available to a workflow triggered from a pull request off a fork, which is exactly the kind of workflow trigger that's been used in real GitHub Actions supply chain attacks to exfiltrate secrets.
Are third-party actions and plugins pinned and reviewed?
Most CI systems, GitHub Actions, GitLab CI, Jenkins with plugins, let you pull in third-party, community-maintained steps by name and a floating version tag. That's the exact mechanism abused in several real incidents, most notably the tj-actions/changed-files compromise, where a widely used GitHub Action was compromised and injected credential-exfiltrating code into every pipeline that referenced it by a mutable tag.
Pin third-party actions to a full commit SHA, not a version tag, since tags can be moved to point at different code after the fact while a commit SHA cannot. Review what a third-party action actually does before adopting it widely, and keep a shortlist of vetted actions rather than letting every team pull in whatever's convenient.
Is your build environment actually isolated per job?
Shared, long-lived build runners that persist state between jobs are a lateral-movement risk: if one pipeline is compromised, whatever state or credentials remain cached on that runner are available to the next job that happens to land on it. Ephemeral, single-use runners that are destroyed after each job close this gap, at some cost to build speed since you lose warm caches, a tradeoff worth making deliberately rather than defaulting to persistent runners purely for speed.
Network egress from build runners is worth restricting too. A build step generally doesn't need arbitrary outbound internet access; if it's compromised and network access is unrestricted, exfiltrating secrets or downloading additional malicious payloads becomes trivial. Egress allowlisting limited to your package registries and required services meaningfully raises the bar.
Are your builds reproducible and your artifacts signed?
If you can't reproduce a build from the same source and get the same output, you have no reliable way to detect if a build was tampered with somewhere in the pipeline. Reproducible builds are a real engineering investment, but even without full reproducibility, signing build artifacts (with something like Sigstore/cosign) and verifying those signatures at deploy time means a tampered artifact that didn't come from your legitimate pipeline gets rejected before it's deployed, rather than trusted implicitly because it showed up in the right registry.
Generating and attaching an SBOM at build time, rather than reconstructing one later from a running system, gives you an accurate record of exactly what went into each build, which matters both for vulnerability response and for demonstrating supply chain integrity to customers doing vendor risk review.
Are dependencies pinned and scanned before they enter the build?
Unpinned dependency ranges mean your build can pull a different, potentially compromised version of a package tomorrow than it pulled today, without any code change on your side triggering it, exactly the mechanism behind several recent npm supply chain compromises where a maintainer account was compromised and a malicious version was published under the legitimate package name. Lockfiles pinned to exact versions, combined with dependency scanning that runs before the build consumes a new version, close most of this gap.
Is the pipeline configuration itself under version control and review?
Pipeline definitions, the YAML files that define what your CI actually does, deserve the same code review scrutiny as application code, because a change to pipeline config can grant new permissions, add a new secret, or introduce a new third-party dependency just as easily as a change to application logic can introduce a vulnerability. Require review on changes to pipeline configuration files specifically, and alert on changes that touch secrets access or add new external actions.
FAQ
What's the single highest-impact fix for CI security?
Moving from long-lived, broadly-scoped secrets to short-lived, narrowly-scoped credentials via workload identity federation. It closes off the most damaging and most common real-world attack path with a change that doesn't require re-architecting your pipeline.
Do we need ephemeral runners if our pipeline is entirely internal to our own network?
Network isolation reduces but doesn't eliminate the risk; a compromised internal dependency or a malicious insider can still abuse a persistent runner's cached credentials. Ephemeral runners are cheap insurance relative to the blast radius they close off.
How do we know if a third-party CI action has been compromised?
You generally find out after the fact via a public disclosure, which is exactly why pinning to a commit SHA rather than a tag matters: it means a future compromise of the action's repository doesn't automatically affect your already-pinned pipelines until you deliberately update the pin.
Should artifact signing be mandatory before every deploy?
For anything reaching production, yes. It's a low-cost control (tools like Sigstore have made it close to free to implement) that closes a real gap: without it, a deploy step has no cryptographic way to distinguish a legitimate build artifact from a tampered one.