Think about what your build pipeline can do. It can read every secret your releases require — cloud keys, signing keys, registry tokens, database credentials. It can run arbitrary code with those credentials, against your source, on your infrastructure. It can write artifacts that your entire company then deploys. And it accepts input from nearly anyone: commits from every engineer, pull requests from outside contributors, dependencies from public registries, base images from public repositories. The pipeline is the place where untrusted input and trusted credentials share a room, and the room is rarely designed with that meeting in mind.
The CI/CD pipeline is the crown jewel of the modern software supply chain. It is also, for most organizations, the least inspected high-value target they own.
Why the Pipeline Is the Target
Attackers do not break into applications because the application is the prize. They break in because the application is a doorway to the pipeline, and the pipeline is a doorway to everything. A single pipeline compromise provides:
- Persistence through your own release process. Malicious code injected at build time survives every audit, every test, and every security review — because the reviews happened after the injection and before the deploy.
- Credential collection. Build runners routinely hold the keys to production, staging, package registries, and code signing. One poisoned step exposes all of them.
- Supply chain reach. If you ship libraries, containers, or SDKs, a poisoned build exports the compromise to every downstream consumer of your software. The attack reproduces itself outward.
- Covered tracks. A pipeline compromise looks like normal build activity. The malicious commit is a regular commit; the poisoned artifact is a regular release; the deleted log line is a regular cleanup.
The Attack Surface of a Typical Pipeline
A modern pipeline is not one machine. It is a web of trust decisions, each of which can be subverted:
| Surface | How it's abused |
|---|---|
| Source control | A malicious commit, a poisoned test fixture, or a submodule pointing at a compromised repo |
| Pull requests | PRs that execute build steps — like dependency installation or test commands — with privileges the author never had |
| Dependencies & base images | Typosquatted packages or tampered container images entering the build through normal resolution |
| CI configuration | Pipeline-as-code files modified to add a hidden step that exports secrets |
| Shared caches & artifacts | Cache poisoning that injects code into every subsequent build using the cache |
| Build plugins & actions | Compromised or typosquatted third-party actions/plugins executed with the runner's credentials |
| Secrets infrastructure | Over-broad vault or secret-store access granted to runners, readable by any job they execute |
The Poisoned Cache
Thought experiment — the artifact that was always there
Your build pipeline caches dependencies in a shared object store to make builds fast. One afternoon, a pipeline job running with elevated permissions — a leftover from a misconfigured deploy step — writes a subtly modified copy of a popular library into the cache key used by the entire engineering organization.
From that moment, every build that resolves that cache key receives the modified library. The modification is small: a few extra lines appended to a post-build hook that quietly exfiltrates environment variables on the first run in each environment. Because the library is pulled from cache, the tamper is invisible in your lockfiles, your SBOMs, and your dependency audit — everything says the correct version was resolved. The cache was not versioned as an artifact; it was treated as infrastructure, so nobody audits what enters it.
Three weeks later, a leaked credential set is sold online, and an investigation traces the leak to the pipeline. The cache was the doorway. Nobody had ever checked the doorway because everybody was busy checking the front door.
Why PRs Are the Favorite Vector
The pull request is the pipeline's most dangerous feature, because it is designed to give untrusted code a controlled execution opportunity. The controls are the problem. When a PR triggers a build, the build may run with repository credentials — including secrets from the default environment — unless explicitly scoped. The difference between "untrusted code runs tests" and "untrusted code reads my production keys" is often a single configuration decision that most teams never consciously make.
The attacker's playbook against PRs is simple: submit a PR that looks like a small, reasonable contribution, and let the pipeline do the heavy lifting. A test file that reads environment variables and posts them to a URL, hidden inside an innocent-looking change, executes with whatever access the PR build has. The ecosystem has responded with read-only tokens for PR builds, sandboxed execution, and mandatory human review of pipeline definitions — but the responses only work if they are actually configured, and the default is usually the permissive option.
Pipeline-as-Code: Config That Executes
Pipeline-as-code made the problem sharper. Build definitions are now stored in the repository itself, reviewed like code, and merged like code. This is a genuine improvement in visibility — and a genuine expansion of the attack surface, because the build definition is now attacker-controllable input in the very system it controls. A compromised repository write — from a stolen token, a vengeful insider, or a merged-but-unreviewed pipeline change — is a full pipeline compromise by construction. There is no separate, protected layer between "who can edit the repo" and "what code runs with secrets."
The defense is architectural: make the pipeline definition itself a protected resource, reviewed separately from application code, with changes gated behind the same ceremony as production changes. Organizations that treat pipeline files as configurable code like any other configurable code are treating the keys to the kingdom as if they were a preference file.
Hardening the Room Where Code Meets Credentials
- Give the runner the least it needs. Scoped, short-lived, read-only credentials for PR builds. No default environment secrets. A job that doesn't need production access should not be able to reach production.
- Separate untrusted and trusted execution. PRs from outside the trust boundary run in an isolated environment with no secrets and no ability to write caches or artifacts.
- Treat cache as an artifact. Content-addressed caches, verified against recorded hashes, so a poisoned cache entry is detectable and every consumer can be traced.
- Pin everything that enters the build. Dependencies, base images, actions, and plugins should be pinned by digest or hash, not by a mutable tag or a floating version.
- Protect the pipeline definition. Pipeline-as-code files get their own review, their own access control, and their own audit trail.
- Sign the output. Artifacts leave the pipeline signed and verifiable, so a tampered build is detected at deployment time rather than after the fact.
- Watch the pipeline like production. Alert on new steps, changed secrets access, out-of-hours builds, and runs that touch resources they never touched before.
The Threat Model Nobody Writes Down
The first step in defending the pipeline is admitting who its adversaries are. The threat model for a build system is not "outside hackers." It is: every contributor who ever submits a pull request, every dependency maintainer whose package your build resolves, every image that enters your pipeline, every employee whose token can trigger a build, and every plugin you run that was written by someone else. The pipeline is a trust boundary that aggregates the trust of all of them. An attacker needs only one of those relationships to go wrong, and the pipeline's entire privilege set becomes available to execute whatever they send.
Writing the threat model down matters because it changes the questions you ask. Instead of "can someone compromise our infrastructure?" you ask "which of the hundreds of inputs that reach our build have the ability to change what it does?" The second question is the one that produces a security plan. The first produces a firewall.
The Frontier Angle
The pipeline is where the supply chain's most interesting security property lives: it is the place where code becomes truth. Before the pipeline, everything is a proposal. After the pipeline, everything is a release — tested, reviewed, signed, and trusted by everyone downstream. An attacker who controls the pipeline controls the transformation itself, and the entire downstream world inherits the result without question.
The organizations that survive this class of attack are not the ones with the strongest endpoint protection or the most advanced detection. They are the ones that recognized the pipeline as the critical trust boundary and hardened it accordingly — with least-privilege credentials, pinned inputs, protected configuration, and the willingness to fail a build loudly rather than accept a quiet compromise. The pipeline is not plumbing. It is the assembly line where trust is manufactured, and it deserves the same security attention as the product it produces.
Key Takeaways
- The CI/CD pipeline is where untrusted input meets trusted credentials; that meeting is the attack surface.
- A pipeline compromise gives persistence, credential access, supply-chain reach, and covered tracks simultaneously.
- Pull requests and pipeline-as-code are the highest-leverage vectors because they convert outside input into privileged execution.
- Least-privilege runners, pinned inputs, protected pipeline definitions, and signed output close the practical gaps.
- The pipeline turns proposals into releases — controlling it means controlling what the world believes about your software.
Most organizations secure the castle and leave the drawbridge unexamined. The drawbridge is the pipeline: the place where everything outside — commits, PRs, dependencies, images, config — is pulled inside and given access to everything that matters. The next major supply chain breach will not be a clever parser exploit. It will be a build job that ran with too much privilege, a cache nobody audited, or a pipeline file nobody reviewed. The fix is not more endpoint agents. It is recognizing that the assembly line deserves the same respect as the product — because in the modern supply chain, the assembly line is the product.