# Software Supply Chain Attacks: Where Modern Build Pipelines Break

> A build pipeline trusts a long chain of things by default: the package registry, the CI runner, every plugin, every base image. Each link is a place a supply chain attack can enter without ever touching your own code.

- URL: https://firewallsync.com/posts/software-supply-chain-attacks-where-modern-build-pipelines-break/
- Site: FirewallSync (https://firewallsync.com/)
- Topic: AppSec & APIs
- Tags: supply chain, appsec
- Published: 2026-10-30
- Author: FirewallSync Editorial


A modern build pipeline trusts a long chain of things by default, mostly without anyone deciding to trust them explicitly: the package registry resolving your dependencies, the CI runner executing your build steps, every third-party action or plugin pulled into that process, the base image your container starts from. A supply chain attack doesn't need to compromise your own code at all — it just needs to compromise one link in that chain, because your pipeline will trust whatever that link delivers.

## Dependency resolution as an attack surface

Package managers resolve dependencies by name and version, largely trusting that a package's identity maps to what it claims to be. This assumption is exactly what dependency confusion attacks exploit — publishing a public package under the same name as an internal one, at a higher version number, so the build system pulls the attacker's code instead of the legitimate internal package. This isn't a bug in any specific package manager; it's a structural consequence of how most of them are designed to resolve names across multiple registries.

Typosquatting works on the same trust assumption from a different angle — publishing a malicious package with a name one character off from a popular legitimate one, betting that enough developers will mistype it or copy a typo from a tutorial to make the attack worthwhile at scale.

## Build-time script execution

Most package managers support scripts that run automatically during install — npm's `postinstall`, for example. This means installing a dependency can execute arbitrary code on the machine running the install, before your application code ever runs, and before any application-level security control has a chance to matter. A compromised or malicious package doesn't need to be imported and called by your code to do damage; simply being present in `node_modules` (or the equivalent for your ecosystem) at install time can be enough.

## CI/CD runners and third-party actions

CI pipelines commonly pull in third-party actions, plugins, or shared workflow steps maintained outside your organization. Each one runs with whatever permissions and secrets access your pipeline configuration grants it — meaning a compromised or malicious third-party action has the same practical reach as a compromised dependency, but often with direct access to CI secrets (cloud credentials, signing keys, deployment tokens) that application dependencies don't typically touch. A popular action being compromised at the source — through a maintainer account takeover, for example — propagates instantly to every pipeline that references it, especially ones pinned to a mutable tag rather than a specific commit.

## Base images and the layers you didn't build

A container build starting `FROM` a public base image inherits everything already baked into that image — and if that base image is compromised upstream, at the registry or at the maintainer level, every image built from it inherits the compromise without any change to your own Dockerfile. This is the same trust-chain problem as dependency resolution, just one layer further removed from your own code.

## Why this class of attack is different from a typical vulnerability

A traditional vulnerability is a flaw in code that needs to be found and exploited. A supply chain attack often requires no vulnerability at all — it just requires the attacker to get their code accepted as a trusted input somewhere in the pipeline, at which point it executes with whatever trust and access that input normally receives. This means traditional vulnerability scanning, which looks for known-bad code patterns or known CVEs, often doesn't catch a well-executed supply chain attack at all, since the malicious package or action may have no publicly known vulnerability associated with it — it's simply doing something it wasn't supposed to do, using access it was never denied.

## Where this leaves defenses

**Pin dependencies and actions to exact versions or commit hashes**, not mutable tags — a tag like `v2` or `latest` can silently point to different, potentially compromised code over time, while a pinned commit hash cannot change without the reference itself being updated.

**Scope your private package names to a registry that doesn't allow public namespace collision**, or explicitly configure your package manager to only resolve certain scopes from your private registry, closing the specific gap dependency confusion exploits.

**Restrict what CI pipeline secrets a given workflow or job actually needs**, rather than granting broad access by default — a compromised third-party action in a job with narrowly scoped secrets can do meaningfully less damage than one running with access to everything.

**Generate and monitor SBOMs across your build pipeline**, not just your application dependencies, so a newly disclosed compromise in a base image or build tool can be checked against your actual exposure quickly.

Supply chain security isn't a single control — it's an acknowledgment that your pipeline's trust boundary extends far beyond your own code, to every registry, runner, action, and base image it depends on, and each one needs to be treated as a real part of your attack surface rather than an assumed-safe utility.

Image credit: Photo by [frank mckenna](https://unsplash.com/photos/tjX_sniNzgQ) on Unsplash
