Software Supply Chain Security

The Five Stages: Source, Dependencies, Build, Artifact, Deploy

A team with mandatory two-person code review, signed commits, and a hardened repository was compromised through its build system. Every control they had operated on the source, and the attacker never touched the source. Controls do not average out across stages. A gap at one stage is a gap, regardless of how strong the others are.


The Concept Explained

The path from an idea to a running process passes through five distinct stages, each with its own trust assumption and its own attacker payoff.

SOURCE ──> DEPENDENCIES ──> BUILD ──> ARTIFACT ──> DEPLOY
   │             │             │           │           │
 what you    what you       what        what is     what runs
  wrote      imported     assembles    distributed
KEY CONCEPT

The property that makes this framing useful: each stage trusts the output of the previous one and cannot verify it. The build system does not know whether the source was tampered with. The registry does not know whether the build was clean. The cluster does not know whether the image in the registry is the one the build produced. Every stage boundary is a trust handoff, and an attacker only needs one of them to be unverified.


How It Works

Stage by stage

StageTrust assumptionWhat an attacker gainsExample
SourceThe code in the repo is what authors intendedCode review is bypassed or subverted; the change looks legitimateRepo compromise, malicious contributor, stolen credentials
DependenciesImported packages do what they claimCode execution inside your build and your runtime, without touching your repoevent-stream, dependency confusion, typosquatting
BuildThe build faithfully transforms source into artifactOutput differs from input, and every downstream signature certifies the resultSolarWinds
ArtifactWhat was published is what was builtSubstitution or modification between build and consumptionCodecov, registry compromise, tag mutation
DeployWhat runs is what was approvedBypassing the entire chain by deploying something elseDirect cluster access, unverified images

Each row is a different problem needing different controls, and they do not substitute for one another.

Why controls do not compensate across stages

The point that the opening scenario makes concrete.

SolarWinds had a compromised build stage. The source repository was not modified. So every source-stage control (review, signed commits, branch protection, static analysis on the repo) was operating correctly and inspecting something that was never attacked. The malicious code was inserted after those controls ran and before the signature was applied, which meant the resulting artifact was legitimately signed by the vendor's own key.

That produces a useful test for any control you are considering:

Which stage does this protect, and what does it assume about the stages before it?

Dependency scanning assumes the build is honest. Image signing assumes the thing being signed is correct. Admission policy assumes the registry content matches what was verified. Every control has an implicit dependency on the integrity of everything upstream of it, and stating that dependency is how you find the gaps.

The handoffs are the interesting part

Attacks cluster at the boundaries, because that is where verification is absent:

Source to build. Does the build system verify what it checked out? Building from a mutable branch means the build system trusts whatever the reference pointed to at that moment. Pinning to a commit digest makes the handoff verifiable.

Dependencies into build. Does the build verify what it downloads? Version ranges resolved at build time mean the artifact depends on what a registry served that day. Lockfiles and digests make it checkable (Lesson 2.3).

Build to artifact. Does anything record how the artifact was produced? Without provenance, the artifact is a blob whose origin is asserted by whoever pushed it. This is the handoff SLSA addresses (Module 4).

Artifact to deploy. Does the deployment verify what it is running? A mutable tag means the cluster resolves the name at pull time and may get something different from what was tested (Lesson 7.3).

The recurring pattern: wherever a name is resolved rather than a digest verified, there is a handoff an attacker can occupy.

Where effort actually goes, and where it should

The distribution of attention in most organisations does not match the distribution of leverage:

Effort spent                        Attacker leverage
  Dependencies   ████████████         Dependencies   ██████
  Artifact scan  ████████             Build          ██████████
  Source review  ██████               Deploy         ████
  Deploy         ███                  Source         ███
  Build          █                    Artifact       ███

Dependency scanning dominates because it is easy to buy and produces visible output. The build system receives the least attention and offers an attacker the most: it holds credentials, has network access, and can modify what ships while remaining invisible to source-stage controls.

That mismatch is worth raising directly when arguing for investment. Module 3 is about closing it.

The stages are recursive

An uncomfortable observation that changes how you scope this work: your build system has its own supply chain.

The compiler, the base image the runner uses, the pipeline actions you invoke, the scanner you rely on, and the signing tool itself are all artifacts produced by someone else's five stages. Your dependency scanner is a dependency.

You cannot follow that recursion to the bottom, and nobody does. What you can do is decide where you stop and be explicit about it, which is the trust anchor question in Lesson 1.4. The practical version is that a small number of components, your build platform, your base images, and your verification tooling, deserve far more scrutiny than the average dependency, because a compromise there invalidates everything downstream including your ability to detect it.

PRO TIP

When assessing an organisation's posture, walk the five stages and ask one question at each boundary: "what verifies the handoff?" In most cases the honest answer at three or four of them is "nothing, we trust the previous step." That list is your roadmap, and it is more useful than any maturity model, because it is specific to how your pipeline actually works.


In Production

  • Map your own five stages before buying anything. The pipeline you have is rarely the pipeline on the architecture diagram.
  • Write down each control's assumption. "This scanner assumes the build is honest" is the kind of statement that makes gaps visible.
  • Prioritise the build stage. It is the least instrumented and the highest leverage.
  • Look for name resolution. Floating tags, version ranges, and branch references are each a handoff where you accept whatever you are given.
  • Treat build platform components as tier-one dependencies. A compromised scanner or signer defeats the controls that would otherwise catch it.
  • Expect to be unable to finish. The recursion is infinite; the deliverable is a documented, deliberate stopping point rather than completeness.

Tradeoffs and Decision Framework

Depth at one stage against coverage across all five. A very strong control at one stage leaves the others as the path of least resistance. Basic coverage everywhere usually beats excellence in one place, because the attacker chooses the weakest handoff.

Verification cost against pipeline speed. Every verification adds latency and a failure mode to the build path. Verifying at the boundaries that matter beats verifying everywhere.

Pinning against currency. Digest pinning makes handoffs verifiable and means you no longer receive updates automatically, including security fixes. That tension is real, and Lesson 2.3 works through it.

Stopping point for the recursion. Scrutinising the build platform deeply is expensive and is the highest-value place to spend. Stopping too early leaves your controls resting on unverified tooling.


Common Mistakes

Assuming strong source controls protect the artifact. SolarWinds' source was never touched.

Buying a tool before mapping the stages. You end up with excellent coverage of a stage that was not your weakest.

Ignoring the handoffs. The controls exist inside stages; the attacks happen between them.

Treating floating tags and version ranges as convenience. They are unverified handoffs with an operational benefit attached.

Exempting build tooling from scrutiny. It is the one category whose compromise disables detection.

Believing any of this is finishable. The goal is a deliberate, documented trust boundary, not completeness.

Assessing maturity by tools owned. The useful measure is how many handoffs are verified, which is usually a smaller number than the tool inventory suggests.