Ask a delivery team to name their dependencies and they will produce a list in seconds — it is what the scanner reports on every week. Ask them to name every system their CI pipeline holds credentials to, and you will usually get a pause.
That asymmetry is the whole problem. You have spent years building controls around what runs in production, and the thing that decides what runs in production sits outside them.
OWASP made this explicit in the 2025 Top 10: Software Supply Chain Failures entered at third, deliberately widened beyond dependency versions to "compromises occurring within or across the entire ecosystem of software dependencies." The build system is squarely inside that scope. Most programmes are still reading it as a patching exercise.
Why the build system ends up ungoverned
Not through negligence. Through a series of individually reasonable decisions.
CI is developer tooling, so it is owned by engineering rather than by whoever owns production access. It needs broad permissions to be useful — that is the point of automation. Its credentials are configured once, by whoever set up the first pipeline, and are never revisited because nothing forces a review. And it is invisible in the usual inventories: it is not a server, not an application, not a user, so it appears in no asset list and no access review.
The result is a system with production-equivalent authority and development-grade governance.
What actually goes wrong
A workflow change is a production change. If an attacker can land a commit that modifies a workflow file, they do not need to compromise production — production will deploy whatever the pipeline tells it to. On many repositories, a workflow file is protected by exactly the same review as a README.
Third-party actions run with your permissions. A typical pipeline pulls in several community actions, each referenced by a moving tag. A tag can be repointed by whoever controls the repository. You are not running the code you reviewed; you are running whatever that tag resolves to this morning.
Long-lived cloud credentials sit in the pipeline indefinitely. A static key stored as a CI secret is the highest-value object in most organisations' build systems: it is broadly scoped, rarely rotated, and valid from anywhere.
Self-hosted runners carry state between jobs. If a runner is reused, one job can leave behind what the next one picks up — cached credentials, modified tooling, a poisoned dependency cache. This is the failure mode that surprises people most, because the pipeline looks stateless.
Forked pull requests can reach secrets. Workflow triggers that run in the context of the base repository will happily execute a contributor's code with your tokens if configured carelessly. It is a well-documented footgun that is still widely deployed.
What to do, in order
1. Inventory what CI can reach
Before changing anything, write down every credential in your CI configuration and what it grants. Not what it is for — what it permits. Those diverge quickly: a credential provisioned to publish an artefact frequently carries write access to a whole subscription because that was the easiest role to attach at the time.
This takes an afternoon and usually settles the argument about whether the rest of the list is worth doing.
2. Replace long-lived credentials with short-lived federated ones
Workload identity federation lets your pipeline exchange its own identity for a short-lived cloud token, scoped to a specific repository and often a specific branch or environment. No stored secret, nothing to rotate, nothing useful to steal.
This is the highest-value change on the list and it is a configuration change rather than a project. If you do one thing, do this.
3. Make permissions least-privilege by default
Set the default token permission to read-only at the organisation level and grant write scopes per job, explicitly. Most jobs need to read a repository and nothing else; the ones that need more will announce themselves when they fail, which is exactly the feedback you want.
4. Pin third-party actions to a commit hash
Not a tag, not a major version — a full commit SHA. It is the difference between "I trust this project" and "I trust this specific reviewed code." Renovate or Dependabot can raise the update PRs, so this costs you a review rather than manual tracking.
Where an action is small and central to your security posture, consider vendoring it. Fewer moving parts than an audit trail you will not read.
5. Make runners ephemeral
A runner should be created for a job and destroyed after it. If you use hosted runners you get this by default. If you run self-hosted, this is the single most important property to preserve, and the one most often traded away for build speed.
If you keep persistent runners for performance, treat them as production hosts: hardened, monitored, patched, and never shared between trust levels. A runner that serves both an internal service and a public open-source repository is a bridge between them.
6. Guard the fork boundary
Require approval before workflows run on pull requests from first-time or outside contributors. Audit any use of triggers that run untrusted code with repository credentials. If a workflow needs both untrusted input and secrets, split it: one job to build the untrusted code with no credentials, another to act on the result.
7. Protect workflow files like deploy configuration
Because that is what they are. Require review from a named owner for changes under the workflow directory, separately from ordinary code review. This is a CODEOWNERS entry and takes five minutes.
8. Log it where your security team looks
Build system audit events should land in the same place as everything else you monitor. The signals worth alerting on are narrow and high-value: a new secret added, a permission widened, a workflow file changed outside a normal review, a runner registered.
The argument that gets it funded
Most security programmes can tell you what runs in production and who can access it. Far fewer can tell you what can change what runs in production.
That is the question worth putting to a steering committee, because it reframes the build system from developer tooling into what it actually is: a privileged system with a deploy path to everything you care about, currently governed by whoever set it up first.
Start with the inventory in step one. It is cheap, it is uncomfortable in a productive way, and it makes the rest of the list argue for itself.
Want help putting this into practice?
We work alongside your team to design, build, and operate the controls described above.