Contributed by Aurélien Coget (Plumber), with Philippe Ensarguet (Orange)
Supply-chain attacks have moved to the delivery layer, just as AI begins redrawing it. Why securing CI/CD has to become an open, community-maintained practice, not a script each team rebuilds alone.
TL;DR
- Pipelines hold the keys to everything: code, secrets, production. Yet they get far less scrutiny than application code. And AI is now writing them.
- Attacks don’t come from one big flaw, but from small weaknesses chaining together.
- Five practical controls close the biggest gaps today.
- The next step is collective: pipeline security as an open, community-maintained standard, where every team benefits from the ecosystem’s best work.
We spend enormous effort reviewing application code: pull requests, linters, static analysis, security scanners. Yet the pipelines that build and ship that code, the ones holding our secrets, our deploy keys, and our production access, are often overlooked.
This is the story of how one silent leak changed the way we think about CI/CD security. Now that pipelines are increasingly written and rewired by machines, that blind spot has stopped being a rare accident and become an industrial-scale target. It’s also a practical guide to closing the gap.
The incident that started it
The plumbing behind software delivery
A few years ago, while working with a client, we found a pipeline that was doing its job perfectly. It built the application, ran the tests, and shipped to production exactly as expected. Nothing looked wrong. The pipeline was green.
Except that, on every single deploy, it was also quietly sending a copy of the source code to an external server.
The application code was clean. The team was doing everything right in the areas everyone looks at. The compromise wasn’t in the code; it was in the plumbing that shipped it. And none of the security tools in place caught it.
That incident led us to a simple, uncomfortable question, which we then put to hundreds of CISOs, CTOs, and DevOps leaders: how many CI/CD pipelines run in your organization, how are they secured, and how long does it take to keep them clean?
We still don’t get a clear answer to all three.
CI/CD pipelines: an attack surface that grows with AI
The reason attackers are moving to this layer is straightforward: CI/CD pipelines hold the highest privileges in most organizations. They have access to source code, secrets, registry tokens, and the right to deploy to production. They run automatically, on every push, often without human review. Compromise one, and you don’t just get code, you get the keys to everything it touches.
Two curves are now accelerating this at once.
Supply chain attacks targeting the delivery layer keep multiplying. SolarWinds, Codecov, the tj-actions compromise, megalodon: what was a rare event a few years ago is now a recurring pattern. The industry has done a good job modeling these threats, with initiatives like the OWASP Top 10 CI/CD Security Risks. But turning those recommendations into controls teams actually apply, continuously, remains hard.
The way we ship software is changing fundamentally. Yes, AI means more code, and more code means more pipelines. But the deeper shift is qualitative: agents are now touching the delivery layer itself. They generate workflow files, edit CI configuration, and open pull requests that change how software gets built and shipped. YAML written by a model, merged after a cursory glance, executed with the highest privileges in the organization. Non-human identities are accumulating write access to the very layer that holds the keys. The factory isn’t just growing faster than ever; its blueprints are increasingly being redrawn by machines, in a layer few teams have the means to watch closely.
More attacks, on a fast-growing surface, that few teams have the resources to monitor continuously. This is the blind spot.
One thing worth stressing: individual misconfigurations are rarely a single open door on their own. Real compromises almost always come from a combination of small, individually-tolerable weaknesses that line up into a path. A concrete, realistic scenario:
- A workflow uses a third-party action using a mutable tag (@v1) rather than an immutable commit SHA.
- That action is maintained by an individual contributor. No review process, no team behind it, a single personal account standing between its code and your pipeline.
- That same workflow has no explicit permissions block, so it silently inherits whatever the repository’s default is. GitHub tightened that default for new repos in 2023, but countless older ones still hand out write access, and nothing in the workflow file tells you which case you’re in.
None of these three, alone, would necessarily raise an alarm. But together, they form a live attack path: the provider of that action gets compromised (or is the attacker himself) and pushes a malicious version under the same tag. On your next build, without you changing a single line, that code runs in your pipeline with a write-access token. It can exfiltrate your secrets, tamper with your repository, or poison your artifacts downstream.
This is, in essence, the pattern behind the tj-actions compromise (CVE-2025-30066), where a hijacked action leaked CI/CD secrets into build logs across thousands of repositories. The lesson isn’t “fix this one setting”; it’s that pipeline security is about seeing how weaknesses chain together, not about ticking individual boxes.
Five controls to start with
Seeing how weaknesses chain together doesn’t mean the individual controls don’t matter; they’re the building blocks you use to remove those weaknesses.
Here are five controls to start with. The first three each cut one weakness out of the path we just walked through: the mutable tag, the untrusted maintainer, the over-privileged token. The last two shut the doors an attacker’s code comes through in the first place: a push to a branch you did not protect, or a pull request from a stranger.
1. Pin third-party actions to a commit SHA. A mutable tag (@v3, @latest) means you execute whatever the provider pushes tomorrow under that tag. A compromised update runs automatically in your pipeline. Pin to a full 40-character SHA instead.
# Risky: mutable tag - uses: tj-actions/changed-files@v47 # Safe: pinned to an immutable commit - uses: tj-actions/changed-files@24d32ffd492484c1d75e0c0b894501ddb9d30d62 # v47
2. Only use actions from sources you trust. Every third-party action is code you execute with your pipeline’s privileges, written by someone you’ve probably never met. Before adopting one, look at who maintains it: an organization with multiple maintainers and a review process, or a single personal account? Check whether the publisher is verified, whether the project is actively maintained, and whether you actually need it at all. Many popular actions wrap a few lines of shell you could own yourself. When an individual account is the only thing standing between an action’s code and your production access, that account’s security becomes yours.
3. Restrict your workflows’ permissions. By default, the token your workflow receives (the GITHUB_TOKEN) often has write access to everything.
permissions: contents: read
4. Protect your main branch. Require pull request reviews, block direct pushes, and disable force-pushes on the branches that matter.
5. Treat pull_request_target as radioactive. The standard pull_request trigger is safe by design: for pull requests coming from forks, it runs without secrets and with a read-only token.
pull_request_target is different: it runs with the base repository’s full context (secrets, write access) while the code under review comes from a stranger. Combine it with a checkout of the PR’s code, and anyone on the internet can open a pull request that executes their code with your privileges.
This “pwn request” pattern has compromised major open source projects. If you genuinely need it (labeling, commenting), never check out or execute untrusted PR code inside it.
Why does this problem belong in open source
Talking with DevOps teams, we kept running into the same pattern. Almost everyone is solving pipeline security the same way: internally, and mostly alone.
One team writes a script to check that actions are pinned to a SHA. Another writes a slightly different one to flag overprivileged tokens. A third has a clever way to detect secrets in logs. What starts as a good idea quickly turns into a new project of its own: a codebase to own, new tasks in the backlog, rules to keep up to date. And this is the catch: the threat landscape moves fast. New attack techniques appear constantly, so a set of checks that was solid six months ago may already be falling behind. Keeping an internal script current is a race most teams can’t win alongside their actual job.
Three things tend to happen next.
First, everyone reinvents the wheel. The same checks get rebuilt in a hundred companies, in a dozen languages, with no shared baseline.
Second, these internal scripts become orphaned. The person who wrote one moves to another team, the threat landscape shifts, and the script slowly stops reflecting reality. Security decays silently, which is the worst way for it to decay.
Third, some teams implement a given control brilliantly; others barely do. But because each effort is siloed, the best implementation never reaches the team that needs it most. Everyone operates at their own local best effort, not the community’s best work.
This is precisely the kind of problem open source exists to solve. The building blocks are already out there in the open: the OWASP Top 10 CI/CD Security Risks, OpenSSF Scorecard, SLSA and others, but they still have to be turned into checks that teams actually run continuously, and kept current by more hands than any single company has. For CI/CD pipelines, what if, instead of a thousand private scripts, we pooled these initiatives into one shared, open catalog of pipeline security controls? A place where the best contribution wins, where the catalog stays ahead of emerging threats because the whole community feeds it, and where every team, large or small, benefits from the collective expertise rather than its own isolated attempt.
That’s the shift: from every team defending its pipelines with whatever it could build in an afternoon to every team draws on what the entire ecosystem has learned. Security stops being a local effort and becomes a shared standard.
Making it continuous and measurable
Applying these controls once is not enough. Pipelines change constantly: new workflows appear, dependencies drift, and a quick fix reintroduces an old risk. Point-in-time audits capture a moment that is already out of date by the next commit.
Two things seem to matter most here.
Make it continuous. The check should run on every pull request and every change to a workflow, the same way tests do. A control that only runs during a yearly audit protects you for exactly one day a year.
Make it a shared language. A long list of findings is useful to an expert but hard for everyone else. Pipeline security involves different vantage points: the DevOps engineer who owns the workflow, the CTO who arbitrates priorities, and the CISO who answers for the risk. Each tends to look at a different artifact, which makes alignment harder than it needs to be.
We think the ecosystem will eventually converge on something simple enough to be produced automatically and read by anyone in seconds, so that when someone says “our pipelines are secured”, everyone around the table understands the same thing.
That’s the bet behind open source project Plumber (https://getplumber.io/): a single grade, A to E, computed deterministically from a community-maintained catalog of controls, run against GitLab CI and GitHub Actions from one configuration. The same pipeline always yields the same grade, and a team can set a threshold that fails the build below it, so the check becomes a gate, not a report gathering dust. The DevOps engineer, the CTO and the CISO finally read the same artifact.
Underneath the grade sits something just as useful: a bill of materials for the pipeline itself. Every third-party action, container image, template and reusable workflow the pipeline pulls in, each listed with a verdict against the controls, and exportable in CycloneDX so it drops straight into the SBOM tooling teams already run. We have a bill of materials for what our software is made of; this is the same idea for the factory that assembles it, the inventory you need before you can reason about how weaknesses chain together.
What matters more than any single tool is the shift it represents: pipeline security becoming a shared, open, continuously updated practice rather than a private script in each company.
AI moved the bottleneck from writing to trusting
A fair question at this point: if frontier models are good enough to write our workflows, won’t they be good enough to secure them too?
Generation and verification are different jobs, and the bottleneck has shifted from one to the other. Writing code is no longer the constraint; reviewing it is. Teams everywhere are discovering that they can generate in minutes what they need days to trust. And the loop keeps closing: more and more teams are automating the full cycle of generation, review and deployment, with no human in between.
For a feature, a probabilistic review may be a trade-off you accept; a bug ships, you roll back. For security, it isn’t: a model is probabilistic by construction. Ask it twice whether a pipeline is safe and you may get two answers, neither of which you can audit, reproduce, or take to a regulator. A security gate needs the opposite properties: same input, same verdict, every time, from a rule a human can read. We never let the developer who wrote the code approve it alone; the principle doesn’t change when the developer is a machine.
The two layers work together, in both directions. Deterministic checks make AI-written code safe to merge: the agent writes, the rules check, a threshold decides. And in the other direction, clear rules make AI genuinely useful for fixing problems: instead of vaguely asking a model to “make the pipeline secure”, you show it the exact rule it failed, let it propose a fix, and run the check again.
As models improve, this loop only gets stronger: better fixes, applied faster, while the verdict stays the same for everyone, with the opportunity to keep a human in the loop. The guardrail doesn’t lose value as AI progresses; it becomes more necessary. The more pipelines are written by machines, the more we need a source of truth that is deterministic, open, and maintained by the people who live with the consequences.
The takeaway
It all started by chance. One pipeline was doing its job perfectly, while quietly shipping our client’s source code to someone else. This raised a new question: how many pipelines do we have, and how can we be sure that they are safe?
That question is only becoming more difficult. The delivery chain is constantly evolving, with new pipelines, dependency updates and quick fixes being introduced every day. And it’s also growing faster than ever, as more of the code running through it is written by machines rather than read by humans.
We spent a decade learning how to review the code we write. We now expect the same rigor from the chain that builds and ships it.
We are convinced that, in the near future, it will no longer be possible to ship or consume software without trusting the chain that produced it. It’s not about the security of the product itself, but the security of the chain behind it. This trust can only come from the community of people who build and secure these pipelines every day, the people who recognize a compromised pipeline when they see one.
Getting there is something the ecosystem builds together, in the open, so that everyone moves at the level of the community’s best contributions rather than each team’s best local effort.