← All guides

Software supply chain risk: why it keeps getting worse, and what to do about it

Published June 12, 2026. About an eight-minute read.

Most of the code running on your site was written by people you will never meet. A typical web application pulls in hundreds of open source packages, a few third party scripts loaded straight from someone else's domain, a build pipeline full of plugins, and a CDN or two. You own and review a thin layer at the top. Everything underneath is trust you are extending to strangers, and that trust is transitive: the packages you picked pulled in packages they picked, and so on, down a tree that is usually too deep for anyone to read.

Supply chain risk is what happens when any link in that chain turns hostile or quietly breaks. The idea is not new. What changed is the frequency. It used to be a tail risk that mostly worried defense contractors. It is now a roughly weekly occurrence that reaches ordinary websites. This guide is about why that shift happened, how bad the recent cases actually were, and the concrete things you can do to watch for it, find it, fix it, and keep it inside a manageable band.

Why it is concerning

The appeal of a supply chain attack is leverage. Breaking into one company gets you one company. Compromising a package that ten thousand companies depend on gets you ten thousand companies, and most of them will install the malicious version automatically the next time their build runs. The attacker writes the code once, and your own tooling does the distribution.

Three properties make this worse than an ordinary bug:

  • It runs with your privileges.A malicious dependency executes inside your application, your build server, or your visitor's browser, with whatever access that context already has. There is no boundary between you and the code you imported.
  • The trust is transitive and invisible. You vetted your direct dependencies, maybe. You did not vet the four hundred packages they brought along. One of those, several levels down, is enough.
  • The blast radius is automated. Lockfile bumps, npm install, base image rebuilds, and CI runs all pull fresh code on a schedule. The same automation that keeps you patched is the delivery mechanism for a poisoned update.

How concerning, really

Concerning enough that the near misses are scarier than the hits. In early 2024 a backdoor was found in xz, a compression library that ships in essentially every Linux distribution (CVE-2024-3094). It had been planted patiently over about two years by a contributor who spent that time earning maintainer trust, and it was built to hook into the SSH daemon on affected systems. It was caught because a single engineer noticed that logins were running about half a second slow and went looking for why. If that one performance oddity had gone unnoticed for a few more weeks, a remote backdoor would have shipped to a large share of the internet's servers. The catch was luck, not process.

Then the ones that actually landed:

  • In June 2024 the domain behind polyfill.io, a script that more than one hundred thousand sites embedded to support older browsers, changed hands and began serving malware to mobile visitors. Every site that included that one script tag was suddenly handing it out. This is exactly the third party script risk that Subresource Integrity exists to blunt.
  • In 2025 a widely used GitHub Action, tj-actions/changed-files, was compromised so that its existing version tags pointed at a malicious commit. The payload dumped the CI runner's memory, secrets included, into the build logs. Thousands of repositories used that action, and many pin actions by tag rather than by commit, so they picked up the bad version without changing a thing on their end.
  • Further back, but still the template for the genre: SolarWinds in 2020, where attackers got into the build system and a signed malicious update was distributed as a legitimate product release to thousands of organizations. Codecov in 2021, where one modified uploader script quietly harvested environment secrets out of CI across many customers.

Underneath the headline incidents there is a constant background rate. The npm and PyPI registries see malicious package uploads every single day: typosquats of popular names, packages that exfiltrate environment variables the moment they install, dependency confusion attempts that try to trick your resolver into grabbing a public package in place of your private one. Most get caught quickly. The point is that the volume is high and the bar to publishing is low.

The shapes it takes

It helps to know the common shapes, because each one has a different defense.

  • Typosquatting. A package named reqeusts or python-dateutil-helper that sits close enough to a real name that someone installs it by mistake.
  • Dependency confusion. Your build references an internal package name; an attacker publishes that same name on the public registry with a higher version number, and your resolver helpfully prefers the public one.
  • Compromised maintainer or account. A real, popular package ships a malicious update, usually because publishing credentials were stolen rather than because the maintainer turned hostile.
  • Poisoned build or CI. The source is clean, but the pipeline that produces the published artifact was tampered with, so what you download does not match what is in the repository.
  • Malicious third party script or CDN. A script you embed by URL (analytics, polyfills, a chat widget, a font loader) changes underneath you, because the host was compromised or the domain changed owners.
  • A transitive surprise. Any of the above, but several levels deep in a dependency you never chose directly and have never heard of.

How to watch for it and find it

You cannot read every line of every dependency, and pretending otherwise is how teams end up doing nothing at all. The realistic goal is visibility plus a fast reaction. A handful of things buy you most of that for modest effort.

Know what you actually ship.Generate a software bill of materials, an SBOM, for your application and your container images. An SBOM is just a complete, machine readable list of every component and version that ends up in your build. You cannot react to a vulnerable component you did not know was there, and the difference between patching the next xz in an afternoon and hearing about it from a customer is whether you can answer "do we use this, and where" in one query.

Pin and lock. Commit a lockfile (package-lock.json, poetry.lock, go.sum, Cargo.lock) so builds are reproducible and a version cannot change under you without a visible diff. Pin GitHub Actions and other CI plugins by full commit SHA, not by tag, because tags can be moved and a SHA cannot. The tj-actions incident landed specifically on the teams that pinned by tag.

Run software composition analysis. Tools like OSV, Dependabot, and the commercial scanners cross reference your lockfile against known vulnerability databases and tell you which of your dependencies, direct or transitive, carry a published advisory. Wire one into your pull requests so a vulnerable dependency is flagged before it merges, not after it ships.

Protect the browser edge. For any third party script you load by URL, add Subresource Integrity so the browser refuses to run the file if its contents drift from the hash you expect. Better still, self host the ones you can, so a remote domain changing hands cannot reach your visitors. A Content-Security-Policy that restricts which origins are allowed to load scripts is the backstop for the ones you forgot about.

Watch the advisory firehose, narrowly. You do not need to read every CVE. You need to know the moment one lands on something you actually run, which is exactly what an SBOM plus an SCA tool gives you. Subscribe to the security advisories for your language ecosystem and your base images, and route them somewhere a human will actually see them.

A quick external scan helps here too. SecureMonk's scan fingerprints the technologies and script libraries your site exposes and matches them against published CVEs, so it will tell you whether your public surface is advertising a version with a known problem, and which third party scripts you are loading without integrity checks.

How to remediate when you find something

Finding a bad component is the start, not the finish. What you do next depends on the shape.

  • A vulnerable but not malicious dependency. Update to the patched version. If there is no patch yet, pin to a safe version and track the fix. If the problem is in a transitive dependency, you may need an override or resolution entry to force the safe version, since you do not control it directly.
  • A confirmed malicious package. Remove it, rebuild from a known good state, and treat anything it had access to as exposed. If it ran in CI, that means rotating every secret that was present in that environment. Install time code can read environment variables, so assume it did.
  • A compromised third party script. Pull the script tag immediately, or repoint it at a self hosted copy with a known hash, then work out the exposure window from your logs.
  • A poisoned build. This is the hardest, because the artifact and the source disagree. Rebuild from source in a clean environment and compare. If you cannot account for the difference, treat the published artifact as hostile.

The reflex that matters most is rotating credentials after any incident that touched your build or CI. Supply chain attacks are usually after secrets rather than your application logic, because secrets are portable and let the attacker move straight on to the next target.

How to manage it as an ongoing thing

You do not solve supply chain risk. You keep it in a manageable band. The teams that do this well tend to share a few habits.

  • Fewer dependencies. Every package you add is a permanent piece of attack surface and a standing maintenance obligation. The cheapest defense available is declining to add a dependency for something you could write in twenty lines. Be especially wary of a package with a single maintainer and a large transitive tree.
  • Least privilege in the build. Your CI does not need admin on everything. Scope tokens to the minimum, give each pipeline only the secrets it genuinely uses, and prefer short lived credentials. The damage from a poisoned build step is bounded by what that step could reach.
  • Slow down the updates that do not need to be fast. Automatic patching of security fixes is good. Automatically pulling the newest version of everything the moment it publishes is how you become the first victim of a compromised release. A short bake time on non security updates lets the ecosystem catch bad releases before they reach you.
  • Keep the SBOM alive. Regenerate it on every build and keep the history, so that when the next xz lands you can answer the only question that matters, which is whether you are affected, in minutes rather than days.
  • Rehearse the boring response. The first time you rotate every CI secret should not be in the middle of a real incident. Know where your secrets live and how to roll them before you need to.

None of this needs a dedicated team or a large budget. It needs you to know what you ship, pin it so it cannot change without you, watch a narrow stream of advisories that actually apply to you, and decide in advance what you will do when one of them turns out to be bad. The attackers are counting on the fact that most sites have done none of that. Being the site that has is most of the battle.