Software Supply Chain Explained: Threats and Controls
Table of Contents

You merged a small pull request this morning. It bumped one library, the tests passed, and the build went green. Somewhere under that bump sits a package you never chose, published by a maintainer you have never met, installed by a script that ran before your application even started. That is the software supply chain, and many teams only see the top of it.
A clean code review of your own files does not tell you who built the compiler plugin, who holds the registry password, or which job signed the container. The rest of this walkthrough treats that gap as a question of provenance and design decisions, not only a count of known vulnerabilities.
What the Software Supply Chain Covers and Why It Matters
A car maker does not stamp every bolt. Safety depends on parts suppliers, the factory floor, and the trucks that deliver the finished vehicle. Software works the same way. Your customers inherit every choice those stages make, even when your application code is clean.

Six stages and who touches them
- Source you write. Developers, reviewers, and the Git host. A stolen maintainer token on your own repo is already a supply-chain event.
- Dependencies you pull. Open-source maintainers, publishers, and registry accounts on npm, PyPI, Maven, and similar indexes. Transitive packages arrive with the ones you named.
- Build tools and runners. CI plugins, compilers, and the machines that compile. A poisoned action changes the binary without changing the diff you reviewed.
- Artifacts. Container images, packages, and binaries. The digest is the identity of what you shipped.
- Distribution. The registry or CDN that hands the artifact to the next environment. A swapped tag can point at a different digest overnight.
- Deployment. Orchestrators, admission checks, and the runtime that executes the result. Here a bad part finally reaches the customer.
Most real applications contain far more third-party code than first-party code, which is why the chain is hard to see. Visibility is the weak point. In 2024, only 21% of respondents in Anchore's survey of more than 100 organizations were very confident they had complete visibility into every dependency in the applications they build. The same year, 54% of organizations surveyed by Synopsys and the Ponemon Institute, covering 1,278 IT and security practitioners, reported suffering a software supply-chain attack in the previous year.
Sketch the chain on one page before you buy another scanner. List each stage, the team that owns it, and the tool that touches it.
That page is also where you start understanding supplier cybersecurity risks, including accounts and keys that sit outside your repo. A flaw in any stage reaches the customer while your own code still looks fine.
Attack Scenarios That Exploit the Software Supply Chain
Risk concentrates where you do not write the code. A peer-reviewed analysis of the Atlantic Council incident dataset, published on arXiv, identified 161 recorded software supply-chain attacks from 2010 through 2021. Attacks exploiting weaknesses in open-source projects increased by 430% in 2020 and by a further 650% in 2021. Across 2010 to 2022, third-party software containing open-source dependencies was the most common codebase-and-distribution-vector combination.
The volume has not cooled. Sonatype's State of the Software Supply Chain report logged 512,847 malicious packages in 2024, a 156% year-over-year increase. A dependency can become risky after you adopt it, through account takeover, a malicious update, or a weakness nobody had numbered yet.
How an attacker walks the chain
Typosquatting starts at the registry. A package named almost like a popular one, think crossenv instead of cross-env, gets installed because a developer mistyped the name. The control is lockfile pinning plus an allowlist of registries, so a typo cannot resolve to the public index.
Dependency confusion uses a name collision. Your internal package is payments-utils. An attacker publishes the same name on the public index with a higher version, and the installer prefers it. Scoped internal names and a private registry that wins over the public one close that path.
Maintainer account takeover keeps the trusted name and swaps the contents. Phishing or a leaked token lets someone publish a malicious version of a package you already trust. Pin exact versions, wait before adopting a brand-new release, and review what changed.
Malicious install scripts fire before your app runs. npm preinstall or a pip setup hook can read tokens and cloud keys on a laptop or a CI runner. Turn install scripts off by default and run installs inside a sandbox with no long-lived secrets.
Build-system compromise skips the package entirely. In the SolarWinds incident of 2020, the build itself was altered, so signed updates carried the implant. Ephemeral runners and signed build provenance make that tampering visible at the gate.
Vulnerable transitive dependencies sit several layers down. A known CVE in a library your library uses never appears in the diff. Software composition analysis with a policy gate is what catches it.
| Attack Pattern | Entry Point | Primary Control |
|---|---|---|
| Typosquatting | Misspelled package name in a public registry | Lockfile pinning and registry allowlists |
| Dependency confusion | Public package shadows an internal package name | Scoped internal names and private registry priority |
| Maintainer account takeover | Malicious version pushed under a trusted name | Version pinning, release-age delay and review of new versions |
| Malicious install script | Code runs during npm or pip install | Disable install scripts by default and run installs in a sandbox |
| Build-system compromise | CI runner or build tool altered before packaging | Ephemeral runners and signed build provenance |
| Vulnerable transitive dependency | Known CVE several layers deep in the tree | Software composition analysis with policy gates |
No single control covers every row. Pinning does not stop a poisoned compiler, and a CVE scanner does not stop an install script that has no CVE yet. Layer the controls so a miss on one row still fails closed on the next.
Managing Open-Source Dependencies Without Losing Control
A dependency tree works like a family tree. You can name the parent you added and still inherit cousins nobody on the team chose. Counting those cousins is a weak signal of risk.
A 2025 study in the Journal of Management Information Systems analyzed 40,049 packages and found that vertical dependency depth was more strongly associated with vulnerable dependencies than horizontal breadth. A shallow tree of a few trusted packages can be safer than a wide tree whose chains run many layers deep. Tight coupling can sometimes reduce risk. It gets riskier as complexity grows.
Provenance is the other half of the question. Who published this version, from which registry, and which design decision said this library was allowed in? A package with no CVE, a high download count, and a role only in CI can still be the one that steals the build token.
Install time is often when that theft starts. Malicious packages frequently run code during installation, reaching for developer credentials, secrets, or other sensitive data before conventional application controls can inspect the resulting software.

Six controls that follow the graph
- Approve new packages in a short review. Look at maintainer count, package age, download pattern, and whether an install script runs. Popularity is not a security property. Record the approval as a design decision, not a chat message.
- Commit lockfiles and pin exact versions. A floating range invites the next malicious publish into tonight's build. The lockfile is the provenance record of what you resolved.
- Route installs through a private registry proxy. The proxy blocks unapproved sources, so a laptop and CI resolve the same set.
- Delay brand-new versions. A short window, often a few days, gives the community time to flag a hijacked release before you take it.
- Track transitive depth in pull requests. Flag a change that adds a deep new chain, even when the direct dependency looks harmless. Depth is the property the study tied more strongly to vulnerable dependencies.
- Record exceptions with an owner and an expiry date. An open-ended waiver becomes permanent the week everyone is busy.
A package used only by the test runner still executes on a machine that holds credentials. Treat dev and CI dependencies as production risk.
AI-assisted coding makes depth worse, faster. A coding agent can add a plausible library, and that library can pull a chain nobody on the team has read. The fix is the same graph, reviewed while the design is still open: which packages are allowed, how deep a new chain may go, and what the proxy may resolve. Put that review inside the planning and pull-request flow, not in a quarterly cleanup, so the threat picture updates when the tree changes. Teams that want a written standard for this can start from open-source policies and turn each rule into a check the proxy or the pull request can enforce.
What an SBOM Contains and How to Trust It
A Software Bill of Materials is the ingredient list of a product. It records each component and how those components relate, so incident response can answer what you shipped.
That list became a procurement expectation after the United States issued Executive Order 14028 on May 12, 2021. The order directed federal agencies to improve software security, and later guidance centered on SBOMs for software supplied to the U.S. federal government. The National Telecommunications and Information Administration minimum-elements framework sets the baseline fields. It also says a new SBOM should be generated when a component is updated or a new build integrates an updated dependency. An SBOM is not a one-time certificate.
Reading the fields with log4j
Say your service logged with log4j-core during the 2021 incident. Each minimum field answers a different question about that one component.
| Field | What It Records | Example |
|---|---|---|
| Supplier name | Who created or maintains the component | Apache Software Foundation |
| Component name | Name of the software component | log4j-core |
| Version | Version string of the component | 2.14.1 |
| Unique identifier | Package URL, CPE or cryptographic hash | pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1 |
| Dependency relationship | Which component depends on which | Application depends on log4j-core |
| SBOM author | Who or what generated the SBOM | CI pipeline build job |
| Timestamp | When the SBOM was created | 2021-12-13T09:15:00Z |
The version and the package URL are what let you match an advisory. The relationship tells you the component is in this service, not in a side project. The author and timestamp tell you which build produced the list, which matters when two teams generate two different inventories for the same release.
Why the list goes stale
Regenerate the SBOM on every build that changes a dependency. A document from last quarter describes last quarter's tree. During an incident you need the digest that is running, not the spreadsheet someone exported for an audit.
Generators disagree. Common tools can omit packages or mishandle a parser, and a missing package is exactly the one you would want to see. In 2025, 40% of organizations with more than 250 employees reported difficulty tracking their software dependencies, versions, and updates, according to a survey covered by The Register.
Check completeness before you trust the file. Diff the SBOM against the lockfile and against what the build downloaded. If a package is in the lockfile and absent from the SBOM, treat the SBOM as incomplete and fix the generator, not the audit narrative. An accurate inventory still does not tell you whether that component is exposed at runtime, who owns the exception, or which design decision allowed it. Those links come from provenance and from the threat model, not from the ingredient list alone.
How to Harden Your CI/CD Pipeline Step by Step
The system that builds and signs software must be more trusted than the code it produces. Otherwise a compromised build can tamper with both the product and its audit trail. NIST SP 800-218, the Secure Software Development Framework, recommends collecting, safeguarding, maintaining, and sharing provenance data for every component of each release, including SBOM information. Useful evidence includes hashes of the final artifact, source files, libraries, build tools, and relevant build events. Attestations should be signed, so a deployment policy can check source identity, builder identity, workflow parameters, and artifact digest before release.

You remain responsible for securing your own repos, CI/CD, secrets, and credentials, and for configuring least-privilege access. A platform cannot hold those keys for you.
Seven steps and the slip that breaks each one
- Run builds on ephemeral runners that are destroyed after each job. A long-lived runner keeps whatever the last job dropped on disk. The slip is a "warm" self-hosted agent that nobody rebuilds.
- Pin third-party CI actions and build plugins to full commit SHAs, not mutable tags. A tag such as
v1can move overnight to a commit you never reviewed. - Grant each job a least-privilege token scoped to one repository and one action. A token that can push to every repo turns one poisoned job into a campaign.
- Store secrets in a vault and issue short-lived credentials. A static cloud key in an environment variable is what install-time malware is written to find.
- Generate an SBOM during the build and bind it to the artifact's immutable digest. An SBOM that is not tied to that digest can describe a different build than the one you deploy.
- Produce a signed provenance attestation that records source revision, builder identity, and workflow parameters. Unsigned JSON next to the binary is a note, not evidence. in-toto and SLSA-style predicates are the usual shape.
- Verify signature and provenance at the deployment gate, and reject promotion on mismatch. This is the decision point. Scanning for a vulnerable package asks one question. The gate asks whether this exact binary came from an authorized source and a controlled build.
If the evidence generator is no more isolated than the build it watches, the attacker edits both.
Map those predicates to the architecture and threat-model decisions you already approved, then enforce them on the pull request and again at deploy. More detail on pipeline checks lives in this guide to CI/CD pipeline security. Treat SBOM generation as an input to that gate, not as a report you file and forget.
Using Continuous Threat Modeling to Catch Risk Early
Most supply-chain choices happen early. Which library to adopt, which service may call which, and which permissions a build job receives are design decisions. A threat model written once and filed away drifts as tickets and repositories change, so the risks it names no longer match the system.
An inventory does not close that gap by itself. A 2025 Lineaje survey reported that 34% of respondents struggled to identify and track open-source components, 29% lacked tools and processes to analyze SBOMs for vulnerabilities, and 47% had not begun or were still evaluating SBOM integration. Common generators can also produce inconsistent inventories, omit packages, or mishandle parsers in ways that hide a vulnerable or malicious component. Even a correct list has limited value when it is detached from architecture, ownership, compensating controls, and pull-request decisions.

The loop that keeps the model honest
Continuous threat modeling refreshes the model from artifacts the team already has, such as tickets, design documents, repositories, and service metadata. We generate threat models and architecture reviews in minutes from those artifacts, instead of waiting on a workshop calendar.
- Map components and trust boundaries. Include registries, runners, and signing keys, not only your services.
- Name the threat at each boundary. A new third-party package, a new CI job, or a broader token is a threat, not a chore.
- Set a control. An approved source list, a required provenance check, or a ban on install scripts.
- Enforce it at pull-request review. We review every code change for alignment with the customer's architecture and security requirements, and a failed check can block the merge.
- Verify at deployment that the approved decision matches the artifact digest, the builder, and the source revision.
| Aspect | Point-in-Time Review | Continuous Threat Modeling |
|---|---|---|
| Trigger | Before a major release or yearly audit | Each ticket, design change or repository update |
| Model freshness | Drifts after the first release | Refreshed as repositories and tickets change |
| Feedback timing | Scheduled workshop or document review | Inside pull requests and developer tools |
| Enforcement | Manual follow-up after the review | Policy checks that can block a merge |
| Audit evidence | Static document that goes stale | Traceable decisions linked to code and deployments |
| Effort | Weeks of scheduling and meetings | Automated from existing artifacts, with human review |
Every review we produce is traceable to an approved design decision or threat model. That chain is what an auditor, or an incident lead, can follow from intent to the exact dependency, build, deployment, and remediation. A static PDF cannot. If you want the loop inside the tools developers already use, start with continuous threat modeling.
Confirm Your Supply Chain Controls This Quarter
Start this week with the one-page map. List the six stages, name the owner of each, and name the tool that touches it. Bring the blank cells to the next planning meeting. A stage with no owner is the stage an attacker will use.
Then work the next ninety days in order, so each step has something real to attach to.
Days 1 to 30. Turn on lockfile pinning and private registry routing for the repositories that ship most often. Set a release-age delay for new versions so a hijacked publish does not land the same hour.
Days 31 to 60. Generate an SBOM for one production service, diff it against the lockfile and the build output, and fix gaps before you scale the generator. Harden one pipeline with signed provenance and a deployment gate that rejects a mismatch.
Days 61 to 90. Write continuous threat models for the three highest-risk services. Link every control to a recorded decision, including the exceptions and their expiry dates.
Measure three numbers, not a dashboard of dozens. Track the share of services with a current SBOM, the count of unapproved packages the proxy blocked, and the count of expired exceptions still open. Review the map, the SBOMs, and the threat models every quarter. Controls only work while they still match the code they protect.
DevArmor keeps a living security context across design, pull requests, and deployment, so supply-chain controls stay tied to the decisions that approved them. We turn the artifacts you already have into threat models, policy checks, and per-release evidence without a separate workshop for every library bump. Visit DevArmor to see how that fits the pipeline you run today.
Table of Contents
Subscribe

