Why Developer Tools Are the Target
A production web server is hardened. It sits behind a WAF, it has network segmentation, it logs every request, and somebody gets paged when it behaves oddly. A CI runner that builds your code is often none of those things. It has permissions to pull from private repositories, push container images, deploy to staging, sometimes to production. It runs whatever tools the build pipeline tells it to run. And it's usually trusted by default.
That trust is the point. When attackers compromise a widely used developer tool, they don't get access to one company. They get access to every company running the tool, at the privilege level that tool runs with. For a scanner or a build helper, that privilege level is often very high, because the tool needs it to do its job.
The pattern shows up repeatedly in OWASP's Top 10 2025, where Software Supply Chain Failures (A03) is called out as its own category. It was added because the industry couldn't keep treating supply chain compromise as a subset of something else. It's a distinct class of problem with its own mitigations.
How the Compromise Actually Happens
There are four patterns that account for most of what gets reported, and they're worth knowing individually because the defences differ.
1. Malicious Maintainer or Account Takeover
A package maintainer adds malicious code, or an attacker takes over the maintainer's account and publishes a malicious release. This is how event-stream (a popular npm package) was used to steal crypto wallets back in 2018, and it's how ua-parser-js ended up shipping a credential stealer in 2021. The XZ Utils incident in 2024 was a long game version of the same pattern, with the attacker spending years building maintainer trust before planting a backdoor.
This is the hardest pattern to detect because the release comes from the legitimate publisher through the legitimate channel. The package signature checks out because the package really was signed by the account that has always signed it.
2. Build System Compromise
The attacker doesn't tamper with the source code. They tamper with the environment that builds the release. SolarWinds Orion is the reference example. The source tree was clean, but the build server injected the SUNBURST backdoor into the compiled artefact. The Codecov bash uploader incident was similar in effect: the published script was modified to exfiltrate environment variables from every CI job that ran it.
Customers who audited the source wouldn't find anything, because the source was fine. The problem was downstream.
3. Dependency Confusion and Typosquatting
Attackers publish a package to a public registry under a name that either (a) typos a popular package or (b) matches an internal package name that a company uses. When a developer fat-fingers the install or the build system pulls from a misconfigured registry, the malicious package wins. This is how Alex Birsan's dependency confusion research demonstrated a single approach working against Apple, Microsoft, PayPal, and dozens of other large companies.
Typosquatting is less sophisticated and catches people anyway. The npm and PyPI registries routinely remove malicious packages named to look like popular ones, and the rate hasn't slowed.
4. Tool-Specific Compromise With Credential Harvesting
The compromised tool is used specifically for its access. The attacker doesn't care about running arbitrary code inside the target. They care about the credentials the tool handles. Security scanners are a natural fit for this pattern because they're given broad read access to inspect what they're scanning. The Trivy compromise that led to the EU Commission breach is a clear example: the attackers used the tool to harvest AWS credentials, then walked into the cloud account.
The Blast Radius Problem
The reason supply chain incidents turn into headlines is that the blast radius is almost always larger than the first compromise. The initial foothold is a CI runner or a build server. From there the attacker collects cloud credentials, API tokens, signing keys, and repository access. Each credential is a lateral move to another system that wasn't on the original target list.
Sonatype's State of the Software Supply Chain report tracks malicious packages discovered across open source registries, and the count has been running into the millions per year. The absolute number matters less than the trend, which has been clearly upward for several years. The economic argument for attackers is simple. One compromise yields many victims, and the barrier to publishing a package is low.
What Actually Reduces the Risk
There's no single control that eliminates supply chain risk. The realistic goal is to make each compromise smaller: fewer credentials exposed, less lateral movement, faster detection. These are the controls that show up repeatedly in post-mortems where the damage was contained, grouped roughly by when they help.
Dependency Controls
Pin Everything and Verify Integrity
Every package manager supports lock files and checksums. Use them. Don't pull "latest" for anything that runs in a privileged context, and verify the checksum on every install. If the checksum of a pinned version changes, that's a signal the release was tampered with or republished, and the build should fail rather than continue.
This applies to CI/CD actions and plugins too, not just application dependencies. Reference GitHub Actions by full 40 character commit SHA instead of mutable version tags like v4. The tj-actions/changed-files compromise in March 2025 demonstrated why: attackers redirected all version tags to a malicious commit, and every workflow using tag references pulled the compromised version. SHA pinning would have stopped it cold. Dependabot and Renovate can auto-update pinned SHAs so the maintenance cost is manageable.
For standalone binaries downloaded from a release page, pin the SHA256 hash and check it on every run. This doesn't protect against a maintainer publishing a malicious release, but it eliminates mid-flight tampering.
Use Trusted Publishers Where Available
npm, PyPI, RubyGems, and crates.io now support trusted publishers, which bind a package to a specific CI/CD workflow using OIDC. Instead of a long lived API token that can be leaked or stolen, your CI system exchanges a short lived OIDC token for publish access. Only the designated workflow in the designated repo can push a new version.
This directly addresses the Codecov pattern, where attackers exfiltrated environment variables including publish tokens from CI jobs. If the token doesn't exist as a stored secret, it can't be exfiltrated. npm also auto-generates provenance attestations that link published packages back to the source commit they were built from, which gives consumers a verification path.
Dependency Review Automation
There's a tension between updating fast (to get security fixes) and updating slow (to avoid pulling in a compromised release before the community notices). The realistic balance is a short lag (days, not weeks) for most dependencies, combined with automated review of what's actually changing in each update.
GitHub's dependency-review-action blocks PRs that introduce dependencies with known vulnerabilities or restricted licences. Socket goes further by analysing package behaviour: does it make network calls, access the filesystem unexpectedly, or contain obfuscated code? That kind of behavioural analysis catches things that CVE databases don't, because a malicious package published yesterday doesn't have a CVE yet.
Enforce npm ci (not npm install) in CI so builds install strictly from the lockfile. If someone slips a dependency change into a PR without updating the lockfile, the build fails.
Build Environment Controls
Least Privilege in CI/CD
The single biggest determinant of how bad a supply chain incident gets is what credentials were reachable from the compromised process. A scanner doesn't need write access to production. A linter doesn't need read access to customer data. A build runner doesn't need long lived cloud admin keys.
Short lived credentials scoped to the minimum resources required turn a potential cloud takeover into a contained incident. This is boring advice that most organisations still haven't applied consistently, because it's more work to configure than a single admin token.
Network Egress Controls
Most supply chain compromises need to phone home. The compromised tool has to send stolen credentials somewhere, whether that's a typosquatted domain, a Cloudflare tunnel, or a GitHub gist. If your CI runners can talk to any IP on the internet, that exfiltration is trivial. If they can only reach a short allowlist of registries and internal services, the attacker's job gets much harder.
Egress filtering on CI/CD runners is one of the highest value controls available and one of the least commonly deployed. A proxy or firewall rule that restricts outbound traffic to your package registries, cloud APIs, and artifact stores would have blocked credential exfiltration in most of the incidents described above. The Trivy compromise relied on sending harvested AWS keys to attacker infrastructure. A CI runner that could only reach the AWS API and the container registry wouldn't have been able to deliver those keys.
For GitHub Actions, StepSecurity Harden-Runner provides network level egress controls as a drop in addition to workflows. For self hosted runners, Kubernetes NetworkPolicy with Calico or Cilium does the same thing. DNS logging is the lighter weight version: even without hard blocking, logging DNS queries from build environments and alerting on unusual domains gives you a detection signal that's hard for attackers to avoid.
Sandbox Build Tools
Running build steps inside sandboxed runtimes restricts what a compromised tool can actually do, even if it gets past other controls. npm install executes arbitrary postinstall scripts by default. A malicious dependency can do anything the build user can do unless the runtime prevents it.
gVisor intercepts system calls and enforces restrictions at the kernel API level, so a sandboxed build process can't access the host filesystem or make unexpected network connections even if the code tries. GKE Sandbox provides managed gVisor for Kubernetes workloads. The performance overhead varies by workload but is typically modest for build tasks, and worth it for the steps that handle untrusted inputs, like dependency installation and third party scanners. You don't need to sandbox everything, just the steps where you're running code you didn't write.
Separate CI from Production
Build environments should not share credentials, networks, or trust boundaries with production systems. If the compromise of a build server can reach a production database, the architecture is doing the attacker's lateral movement for them. The fix is network segmentation and account separation, not trust in the build tools.
Two Person Review for Pipeline Changes
Changes to CI/CD workflow files, Dockerfiles, and build scripts should require review from a second person, ideally someone with security context. GitHub's CODEOWNERS feature lets you require approval from a specific team for changes to .github/workflows/, Dockerfile, or Makefile paths. The XZ Utils backdoor succeeded partly because a single maintainer controlled the build configuration unchecked.
Verification and Detection
SLSA Build Provenance
The SLSA framework (Supply chain Levels for Software Artifacts) generates cryptographically signed metadata documenting how an artefact was built: which source repo, which build platform, which inputs. At Level 1, CI/CD generates provenance automatically. At Level 2, the build platform signs it. At Level 3, builds run in isolated, ephemeral environments so that even compromised build credentials can't forge provenance.
GitHub Actions has native SLSA support through slsa-github-generator. If you're already on GitHub Actions, reaching Level 2 is a few lines of workflow config. Level 3 requires fully managed hermetic builders, which is a bigger investment. But even Level 1 gives consumers something they can verify, and that shifts the economics: an attacker who can't produce valid provenance for a tampered artefact has a harder time getting it deployed.
Artifact Signing With Sigstore
Sigstore's cosign lets you sign container images and other artefacts without managing long lived signing keys. It uses the OIDC identity from your CI/CD system to issue a short lived certificate, signs the artefact, and records the event in an immutable transparency log (Rekor). Consumers verify with cosign verify.
This addresses the key management problem that has kept most teams from signing artefacts at all. There are no persistent keys to rotate, no key to steal, and the transparency log means signed events are publicly auditable. Kubernetes, Distroless, and Chainguard images all use Sigstore. On the consumer side, Kyverno admission policies in Kubernetes can enforce that only signed images get deployed.
SBOM for Incident Response
Software Bills of Materials get talked about as a prevention control and they aren't one. What an SBOM gives you is the ability to answer "am I affected?" quickly when a new compromise is disclosed. That's a response capability, not a prevention one, and it's valuable.
The useful version is automated: SBOM generated on every build, stored alongside the artefact, and queryable when an advisory drops. The version that isn't useful is a PDF generated once for a compliance checkbox and never looked at again.
Monitor Build Environment Behaviour
Security scanners scanning for secrets is expected. A security scanner making outbound connections to a new domain is not. A linter reading your AWS credentials file is not. CI/CD environments have predictable behaviour patterns, and deviation from those patterns is a strong signal. The detection gap in most supply chain incidents is days or weeks, not minutes, and it's usually because nobody was watching the build environment the same way they'd watch production.
The Trust Question Nobody Likes
The uncomfortable truth is that modern software depends on thousands of dependencies maintained by people you'll never meet, and you can't audit all of them. Neither can anybody else. The open source ecosystem works because of a network of implicit trust, and that network is exactly what supply chain attackers target.
The realistic answer isn't to vet every dependency. That doesn't scale. It's to accept that some compromise is inevitable over a long enough timeline, and to build the architecture so that one compromise doesn't become a breach. Defence in depth stopped being a cliché when the perimeter stopped existing.
In practice, that means treating CI/CD as a production environment with production grade controls, not as a developer sandbox. It means monitoring the tools you use to build software the same way you monitor the software you ship. It also means assuming at least one of your dependencies will be compromised at some point, and asking what happens next.
Know What's Exposed Before Attackers Do
Supply chain attacks almost always start with reconnaissance of the target's external attack surface. Exposed CI dashboards, forgotten staging environments, and admin panels give attackers a shortlist of entry points to try once they have credentials. Luna maps internet facing assets continuously and checks them against 11,000+ security templates, so the surprises show up in your dashboard instead of someone else's. See how external scanning works.