Your dependency graph is part of your attack surface

A software bill of materials tells you what you ship. Controlling supply-chain risk needs three further things: provenance for the build, a policy for adoption, and a rehearsed route to replace a component quickly.

Categories: Security engineering Series: Secure by design

An inventory is not a control

Software bills of materials have become a procurement expectation, and producing one is now straightforward: the build system emits it, the format is standardised, and the minimum elements are well defined. Many organisations have concluded from this that supply-chain security is largely handled.

An inventory tells you what you ship. That is genuinely useful—when an advisory lands, knowing within minutes whether you are affected is worth the investment. But it changes nothing about your exposure. A complete, accurate, continuously generated bill of materials for a system with a compromised build server, an unreviewed adoption process and no way to replace a transitive dependency describes the problem in high resolution.

Three further capabilities do reduce exposure: provenance for how the artefact was produced, a policy for what enters the graph, and a rehearsed path for getting something out of it.

Provenance answers the question an SBOM cannot

An SBOM asserts contents. Provenance asserts origin: this artefact was produced by this build system, from this source revision, with these inputs, and here is a signed statement to that effect that you can verify without trusting the party who handed you the artefact.

The distinction matters because most consequential supply-chain incidents have not involved a known-vulnerable library. They have involved a legitimate-looking artefact that did not correspond to the reviewed source—a build server that injected code, a release pipeline with a writable step, a package registry account that pushed a version nobody tagged.

The SLSA framework describes this as escalating levels of build integrity, and the early ones are achievable without restructuring how a team writes software: build on a hosted service rather than a developer machine, generate provenance automatically, and verify it before deployment. The verification step is the one teams skip, and without it the attestation is a file rather than a control.

Two practical implications follow. First, the deploy path must reject an artefact whose provenance does not validate, which means provenance verification is a blocking gate. Second, the source revision in the attestation must be the revision that was reviewed—which is only meaningful if the branch it came from enforces review, and if the build cannot be triggered from an arbitrary revision.

Decide adoption before the deadline

Most dependencies enter a codebase during a sprint, chosen by whoever needed them, on the evidence of a search result. The decision is rarely revisited and is almost never recorded.

A lightweight adoption policy changes the default without slowing delivery much. Useful criteria, applied proportionally to where the dependency sits:

Question Why it matters
How many maintainers have commit access, and is release automated from source? A single-maintainer package with manual publishing is a single point of compromise
Does it run at build time, at request time, or in a privileged context? A build-time dependency executes on your build system with its credentials
How large is its own transitive graph? Adopting one package often means adopting forty
Is there a vulnerability disclosure process and a recent security fix? Absence of both usually means absence of attention rather than absence of defects
What is the replacement cost if it becomes unmaintained? This is the number that determines whether you can act later

The last question is the one worth insisting on, because it is the only one about your own resilience rather than the upstream’s behaviour. A dependency with a high replacement cost in a privileged position is a commitment, and commitments should be made deliberately.

NIST SP 800-161 frames the same territory at an organisational level, connecting acquisition decisions to the technical controls that have to exist afterwards. The connection is the part that usually breaks: procurement asks questions at purchase, engineering adopts components continuously, and the two processes do not meet.

Build systems are higher-value targets than libraries

An attacker who compromises a popular library reaches everyone who uses it, after a delay, subject to review. An attacker who compromises your build system reaches your customers directly, signed by your key, with your provenance attached.

This reordering of priorities is uncomfortable because build infrastructure is usually the least-governed part of an engineering estate. Typical conditions include long-lived credentials with broad permissions, pipeline definitions editable by anyone who can push, caches shared across projects, and self-hosted runners that persist state between jobs.

The countermeasures are ordinary infrastructure hygiene applied somewhere it often is not:

  • ephemeral runners, so nothing persists between builds;
  • pipeline definitions protected by the same review requirement as source code;
  • short-lived, workload-scoped credentials in place of stored secrets;
  • signing keys held in a hardware-backed service that logs every use, not in a pipeline variable;
  • separation between the build that produces an artefact and the process that publishes it.

None of this is novel. It is worth stating because a team that has invested heavily in dependency scanning and left its build system open has secured the lower-value target.

Rehearse the replacement, not the patch

When a serious advisory lands, the question is not whether you can identify affected systems—the inventory answers that. It is how long it takes to ship a version without the affected component, or with it replaced.

Most organisations discover their answer during the incident. Rehearsing it instead is cheap: pick a mid-tier dependency, remove it, and time the work end to end, including the tests that break for unrelated reasons and the service that turns out to embed an older copy.

Track two numbers from the exercise. The first is time to patched release for a component with an available fix. The second, and the more revealing, is time to replacement for a component with no fix—an unmaintained package, an abandoned upstream, a library whose maintainer declines to treat the issue as a vulnerability. A programme that can do the first in hours and has no answer for the second is prepared for the easier case.

Prioritisation during the real event should follow exploitation rather than severity. CISA’s Known Exploited Vulnerabilities catalogue is the most useful single input, because it distinguishes what is being used from what could be.

What a customer or regulator will ask for

The external demands are converging on the same set. Enterprise procurement asks for a bill of materials, a disclosure policy and a patch commitment. The EU Cyber Resilience Act requires manufacturers of products with digital elements to identify and document components, address vulnerabilities without delay and provide security updates for a defined support period.

Read as engineering requirements rather than paperwork, these reduce to capabilities: you can say what is in a release, you can receive and act on a report, and you can ship an update to a deployed version for as long as you promised. The third is the one that catches organisations out, because supporting an older release branch requires build reproducibility and test coverage that a fast-moving main branch does not automatically provide.

The useful sequencing is to build the capability and let the document fall out of it. An SBOM generated by a build you can reproduce, for an artefact whose provenance verifies, from a dependency set someone chose deliberately, is a credible answer. The same document produced by a script at audit time is a description.

Get the agent release checklist

Working through this decision?

Establish provenance, adoption policy and a tested route to replace a dependency.

A little more clarity

Questions worth asking.

Start with the questions that shape a better decision.

Is an SBOM enough to satisfy a customer's supply-chain question?

It answers what you ship. It does not answer how the artefact was built, who could have influenced it, or how quickly you could replace a component. Customers increasingly ask the second and third questions.

Should we pin every dependency to an exact version?

Pin, and automate the update. Pinning without an update mechanism produces a codebase that is reproducible and years behind; automated updates with a good test suite give you both properties.

How do we handle a dependency with no maintainer?

Treat it as a component you now own. Either vendor it deliberately with a named internal owner, or schedule its replacement. An unmaintained dependency in the critical path is a decision, so make it explicitly.