Supply Chain Attacks: When the Software You Trust Is the Problem

Traditional security assumes the dangerous code arrives from somewhere obviously untrusted. Supply chain attacks invert that model. The malicious component may arrive through a signed update, a trusted maintainer, or a dependency your team never knew it was running.

A single brass chain with one link compromised on dark wood, dim warm amber side light, deep navy shadows, no people, no logos.


Most security programs are built on a quiet assumption: the dangerous code sits as the code you did not approve. It came from a download you did not perform, an attachment you did not expect, a website you did not visit. The model has served the industry well, and the model is now wrong more often than it is right.

Supply chain attacks invert the assumption. The malicious component arrives through a signed update from the vendor you use, an open source package your developers installed on Tuesday, a build server your team trusts by default, a maintainer account that has been quietly compromised for months, or a transitive dependency that was added to a tool you depend on by someone you have never met. The compromise serves as the trust.

What the last few years actually looked like

Four incidents are worth understanding in detail because they illustrate how different the attack paths really are, and how flattening them into a single story loses the lesson.

SolarWinds (2020). Attackers compromised the build environment of SolarWinds’ Orion product and inserted malicious code into a routine software update. The update was signed by SolarWinds. It was distributed through the normal update channel. It was installed by roughly 18,000 organizations, including US federal agencies and Fortune 500 companies. The malicious code was a patient backdoor that waited for additional instructions. The intrusion was only discovered because FireEye, one of the affected customers, noticed an attacker using a token that should never have been valid. CISA’s AA20-352A and the subsequent Joint Advisory remain the authoritative write up.

3CX Desktop Client (2023). A trading focused threat actor compromised a developer’s machine at 3CX, then trojanised the build pipeline to inject malicious code into the desktop client application. The compromised application was signed and distributed through the normal update mechanism, reaching 3CX’s customer base, including critical infrastructure. The same actor, in the same campaign, compromised a separate product through a different supply chain path. Mandiant’s write up runs as the public reference.

XZ Utils backdoor attempt (2024). A maintainer with years of patient social capital on the XZ Utils project, used by most major Linux distributions, gradually introduced changes that, in their final form, would have allowed a remote, pre authenticated code execution against the SSH daemon on affected systems. The backdoor was caught by Andres Freund, a PostgreSQL developer, who noticed an unexplained latency in SSH logins. CVE-2024-3094 counts as the technical reference. The attack never made it into a production release of a major distribution, which is why the story ended as a near miss rather than a disaster.

npm typosquatting and malicious package campaigns (continuous). Attackers publish packages with names that look almost identical to popular libraries. The malicious packages run during install because of a postinstall script, exfiltrating credentials, environment variables, and crypto wallets. The npm registry removes them as fast as it can, and the attackers publish new ones the same day. GitHub has published ongoing reports on the campaigns and the registry side mitigations.

These are not the same story. SolarWinds was a build server compromise against a closed source vendor. 3CX was a developer account compromise that chained into a build pipeline compromise. XZ was a slow, social engineering takeover of a maintainer identity on a foundational open source project. npm campaigns are continuous, opportunistic, and small per package. The controls that help in one do not necessarily help in another. The thing they share is that the trust was the vulnerability.

A dependency tree with one poisoned transitive package far below the application, showing how a small hidden component reaches many systems.
The dangerous component rarely announces itself. It runs as the dependency the team forgot it had.

How the attack paths differ

The taxonomy matters. If you understand which paths you are exposed to, you can prioritize which controls matter.

Compromised build infrastructure. The attacker gets into the build server and modifies the build to include the malicious code. The resulting binary is signed by the legitimate signing key. This is SolarWinds and 3CX. The defenses are hardening the build environment, separating build credentials from production credentials, building from source where possible, and verifying the resulting binary against the source.

Trojanised signed updates. The attacker modifies the update mechanism itself, or the package that is distributed through it. Defenses include update signing, out of band verification, reproducible builds, and a build process that an external party can re run and compare to the published artifact.

Compromised developer identities. The attacker compromises a developer’s laptop, browser session, or account, and uses that access to introduce malicious code into a project, a build, or a deployment. The XZ Utils case began with patient social engineering of a maintainer. Defenses include phishing resistant MFA on all developer and maintainer accounts, hardware security keys for the highest value identities, and a culture that treats unusual contributor behaviour with appropriate scrutiny.

Maintainer takeover or social engineering. The attacker convinces the project to hand the project to them, or social engineers a maintainer into accepting a change that looks innocuous. The XZ Utils backdoor was introduced through a series of changes that, individually, looked reasonable. Only in aggregate did they form the backdoor. Defenses include a maintainer change review process, two party review on sensitive projects, and a clear path for a maintainer to escalate a request they are not sure about.

Malicious dependencies. The attacker publishes a package that looks useful, the package gets installed, the postinstall script runs. Defenses include reviewing new dependencies, blocking or reviewing install scripts, using protected scopes or internal registries, and pinning versions.

Typosquatting. The attacker publishes a package with a name one character different from a popular one. The defender installs the wrong one. Defenses are name discipline, lockfiles, and package reputation.

Dependency confusion. A package is installed by name, the public registry serves a higher version number than the internal registry, and the public version is malicious. Defenses include namespace reservation on the internal registry, package signing, and tooling that detects version anomalies.

Build Script and install script abuse. The malicious behaviour runs in a postinstall or preinstall script, which the developer never reads. The fix is to block or sandbox install scripts in environments where the package is not yet trusted.

CI/CD secret compromise. The build pipeline has access to production credentials. The attacker compromises the build pipeline. The fix counts as the same as the build infrastructure compromise, with the additional discipline of scoping CI/CD credentials to the specific resources they need and rotating them aggressively.

Unreviewed transitive dependencies. The application depends on a library, which depends on another, which depends on another, and the dependency chain includes a package nobody has ever heard of. Defenses include SBOMs, dependency pinning, and an emergency dependency removal process.

Package Name substitution. A trusted package changes ownership, gets a new release, and the new release is malicious. Defenses include pinning versions, monitoring for ownership changes, and a process to react quickly when a dependency is transferred or sold.

Vendor remote access compromise. The attacker compromises the remote access tooling the vendor uses to support its product, and uses it to move into the customer environment. Defenses include requiring phishing resistant MFA on vendor remote access, jump hosts with session recording, and the principle of least privilege on what the vendor account can do.

What an SBOM does, and what it does not

The industry has converged on the Software Bill of Materials (SBOM) as a primary supply chain control. The honest version of the conversation is that SBOMs do some things well, and they do not solve the problem on their own.

An SBOM is an inventory. It is a list of the components in your software, the versions of those components, and the relationships between them. The NTIA’s SBOM work and the SPDX and CycloneDX standards are the reference points. An SBOM lets you answer the question “are we using this affected component?” quickly when a new advisory drops. It is genuinely useful for incident scoping, for license compliance, and for finding exposure to a known component.

An SBOM does not, by itself, tell you whether the component was malicious when it was built. It does not tell you whether the build pipeline was compromised. It does not tell you whether the listed source matches the shipped binary. It does not tell you whether your organization could respond to the finding in time. It does not tell you whether the dependency should have been trusted in the first place. It does not tell you whether provenance and signatures were verified.

SBOMs are an inventory system. Inventories are a foundation. They are not a finished defence.

What a small company can actually do

The list of things a Fortune 500 can do about supply chain security is long. The list a 50-person company can do is shorter, and more useful.

  1. Minimize the dependency count. The fewer things you depend on, the fewer things can hurt you. A regular review of “do we still need this?” is a supply chain control.
  2. Use lockfiles and deterministic builds where the market supports them. A lockfile pins the exact version and hash of every dependency. Deterministic builds let you re run the build and compare. Either reduces the surprise surface.
  3. Pin important dependencies. When a package is security critical, pin it, review the upgrades, and read the diff between the version you use and the version you would upgrade to.
  4. Review unexpected package changes. A package that adds a postinstall script, takes on a new maintainer, ships a major version with a small changelog, or adds a network call to a domain you have not seen before is worth a second look.
  5. Use protected package scopes or internal registries. For organization internal packages, reserve the scope on the public registry, and route installations through the internal registry. This kills most dependency confusion.
  6. Block or review install scripts. In environments where the dependency is not yet trusted, run npm or pip with install scripts disabled. Trust the script after review, not before.
  7. Scan dependencies and containers. Tools like OpenSSF Scorecard, npm audit, pip audit, Trivy, Grype, and the major commercial scanners catch the known bad versions. None of them catch the unknown bad versions, but the known bad ones are worth catching.
  8. Use secret scanning in CI/CD. GitHub, GitLab, and Bitbucket all have native secret scanning. Turn it on. Also turn on push protection, which blocks the commit in the first place.
  9. Protect maintainer and build accounts with strong MFA. phishing resistant MFA on every account with merge or publish rights. Hardware keys for the highest value identities.
  10. Separate build credentials from production credentials. The credential that builds the artifact is not the credential that deploys it, runs it, or has access to production data.
  11. Track critical vendors. Know which vendors are security critical. Subscribe to their advisories. Have a documented response when one of them ships a bad update.
  12. Maintain an emergency dependency removal process. When a dependency is confirmed compromised, the organization should be able to remove it, replace it, or pin to a known good version, in hours, not weeks. The process is a write once, practice often artefact.
  13. Know which applications contain a newly affected component. This runs as the SBOM doing useful work. When the next advisory drops, the answer to “are we affected?” should take an hour, not a month.

Where SLSA, Sigstore, and OpenSSF Scorecard fit

These tools are useful in the right places. They are not a finished answer on their own.

SLSA (Supply Chain Levels for Software Artifacts) is a framework that defines levels of build integrity. A project that meets SLSA Level 3 has built its artifact in a hardened, auditable build environment, with provenance that an external party can verify. This stands as the level that defends against the SolarWinds pattern. It is expensive, but for the most critical software, it is worth the cost.

Sigstore is a set of tools for signing and verifying software artifacts. It is a real, working implementation of provenance, used in the Python and Rust markets and increasingly elsewhere. It does not, by itself, defend against a compromised build environment, but it makes the compromise visible.

OpenSSF Scorecard is a quick assessment of a project’s security practices, scored on several factors including branch protection, dependency update tooling, code review, and CI tests. It is a useful first pass filter. It is not a substitute for actually reading the code, the changelogs, and the maintainer history.

None of these are a finished supply chain security program. They are building blocks. The finished program stands as the one your team has the discipline to operate.

The small company supply chain checklist

Use this against your own software, whether you ship a product or operate an internal application. If you cannot answer yes to most of these, the gap is more important than the next dependency you add.

  • You have an inventory of the components in the software you ship or run.
  • The inventory covers transitive dependencies, not just the ones you installed directly.
  • Lockfiles are committed, and CI fails if a build is not reproducible from the lockfile.
  • Install scripts are blocked or sandboxed for untrusted dependencies.
  • Secret scanning is on in every repository and every CI pipeline, with push protection.
  • Every maintainer and build account uses phishing resistant MFA.
  • Build credentials and production credentials are separated.
  • You subscribe to advisories for the components that matter most.
  • You have a written, practiced process to remove a compromised dependency in hours.
  • You know which of your applications contain a given component when a new advisory drops.
  • You review significant dependency upgrades before adopting them, including the maintainer history and the diff.

The point

Supply chain security is not a product. It is a discipline that combines dependency discipline, build integrity, identity protection, and the operational practice of testing the whole system end to end. The good news is that the discipline, applied at moderate cost, defends against the vast majority of supply chain attacks the industry is actually seeing. The bad news is that the attacks will keep getting better, and the discipline has to keep being applied.

Apply it anyway. The cost of getting it right is a weekend of work, repeated a few times a year. The cost of getting it wrong is a year of incident response.

Archive note: This article was added retrospectively to the Human Required 2026 archive. Its research and links were reviewed when it was added.

Abstract supply chain attack as glowing cyan poisoned package floating into a network on dark navy, chiaroscuro from above, no people, no logos.
Supply chain attacks in 2026: 3 things the attack looks like, 3 things the defender has to track, 3 moves for the engineering team.

The bottom line

The patterns the post covers have been showing up in production for long enough that the patterns have names, the failures, the mitigations, the gaps. The work the security team and the engineering team and the operations team are quietly doing today sits as the work that decides whether the practice the post names sits as a tool the team uses or a liability the team is paying for.

Sources & Further Reading

All claims in this article are sourced from primary documentation, vendor advisories, and reputable security researchers.

Spotted an error? Email the editor. Corrections are issued with a visible correction note.

Editorial standards. Every article on humanrequired.org is reviewed by a human editor before publication. AI may assist with drafting or research; final editorial control is human. Read the full standards.

Continue reading