You can review every line your own team writes and still ship an attacker's code. Most of what runs in production was written by strangers, arrived automatically, and was never read by anyone on your side. That is the supply chain, and attacking it is now the efficient way in.

What a supply chain attack is

A software supply chain attack compromises something you depend on rather than something you built. The attacker never touches your servers. They get their code into a package, a build tool or an update that you install voluntarily — and your own automation carries it the rest of the way.

The reason this has moved from a niche concern to the central one is arithmetic. Attacking a company directly means getting past whatever defences it has, one company at a time. Compromising a package that ten thousand companies depend on means doing the work once. If even a fraction of those companies are worth robbing, the return is not in the same order of magnitude.

It is also quiet. A malicious dependency does not break the build — a payload that broke things would usually be found and removed the same day. It works perfectly and does one extra thing.

The five routes in

These are not equally likely, and they are not equally defensible. Sorted roughly from most common to most sophisticated:

1. Publish something malicious and wait for a mistake

The cheapest attack: register a package that a developer might plausibly install by accident, and wait. It comes in three shapes, each with its own mechanism — typosquatting exploits a mistyped name, slopsquatting exploits a name an AI assistant invented, and dependency confusion exploits your build asking a public registry about an internal name.

What unites them is that nothing was compromised. The attacker published a package under a name nobody had claimed. There is no vulnerability to patch, which is why detection is the only defence.

2. Take over a package people already trust

Rather than getting you to install something new, take control of something you already have. The usual route is the maintainer's account: a reused password, no second factor, a phishing email good enough to work on a tired person. A dormant package with an inactive maintainer is the version of this that is easiest to spot in advance.

The instructive case is ua-parser-js in 2021 — a package downloaded millions of times a week. An attacker published malicious versions from the maintainer's own account, and they were legitimate releases in every technical sense: correct name, correct account, published through the maintainer's own credentials on the normal channel. Anything that trusted the name trusted the payload.

This is the argument for a lockfile. The version pin is what saves you here: a malicious new release does not arrive until someone deliberately updates, and that update is a diff somebody can look at. The integrity hash recorded alongside it covers the rarer case — a registry, mirror or proxy serving different bytes for a version you already resolved.

3. Become the maintainer

The most patient version, and the one that should worry you most. Open-source maintainers are frequently unpaid, overworked and looking for help. That is not a weakness to be sneered at; it is the condition most of the software you run is produced in. It is also a social attack surface.

event-stream in 2018 is the simple version: the original maintainer, no longer using the package, handed it to a volunteer who had been contributing helpfully. The volunteer then added a new dependency of his own, and the payload lived in that — targeting one specific cryptocurrency wallet. Nobody who trusted event-stream had ever heard of the package that actually attacked them.

xz-utils in 2024 (CVE-2024-3094) is the version that should change how you think about this. An account spent roughly two years making genuine, useful contributions to a compression library, gradually earned co-maintainer status, and then introduced a backdoor into the release tarballs — not the source repository — aimed at the SSH daemon on the distributions that had patched it to link the library indirectly. It was found before reaching most stable distributions, and it was found by accident: an engineer investigating a half-second delay in a login benchmark.

Be honest about what that means. No dependency scanner would have caught it on the day it shipped. The code was in a trusted package, published by a trusted maintainer, and no advisory existed. What catches this class is the nightly re-check after the advisory lands — measured in hours, not the weeks until your next scheduled scan.

4. Compromise the build rather than the code

The source can be clean and the artefact still malicious, if what turns one into the other is compromised.

SolarWinds in 2020 is the canonical case: the build system was altered so that malicious code was inserted during compilation. The result was signed with the company's own key and distributed through the normal update channel. Customers verified the signature, and the signature was genuine.

Codecov in 2021 is the version most companies should actually worry about, because it needed no state-level effort. A widely-used CI script was modified to send the environment variables of every build that ran it to an attacker's server. CI environments hold cloud credentials, registry tokens and deployment keys — so the payoff was a harvest of other companies' secrets.

This is why pipeline configuration deserves the same review as application code. It runs with more privilege than your application does.

5. The maintainer turns

Rare, and worth naming because it breaks the mental model. In 2022 the author of node-ipc added code to his own package that overwrote files on machines with certain IP addresses, as political protest. No compromise, no attacker — the person the package trusted by design decided to do harm.

You cannot defend against this with better authentication. It is an argument for pinning versions and for not updating dependencies blindly, which is a less comfortable conclusion than it sounds.

Why this is genuinely hard to defend

Three properties, and it is worth being clear-eyed about them:

You cannot read it. A typical application resolves to several hundred packages, most of which nobody on your team chose. Reviewing them is not a matter of discipline; it is not possible.

The dangerous code often runs before you use it. Several ecosystems execute install scripts, so a package can act without ever being imported. The window between "npm install" and "wait, what is this" is where the payload already ran.

Trust is transitive but attention is not. You chose forty packages. Those brought six hundred. Your trust in the forty was a decision; the six hundred inherited it without anyone deciding anything.

What actually reduces the risk

In rough order of how much they buy you per unit of effort:

  • Know what you are running. Every defence below is unavailable until you can enumerate your dependencies. This is the practical purpose of an SBOM — not the document itself but the ability to answer "are we affected?" in minutes.
  • Commit a lockfile, with integrity hashes. Exact versions, recorded in version control, so a change appears in a pull request rather than on one machine. This single habit closes the republish attack and makes builds reproducible.
  • Do not run install scripts you did not ask for. npm ci --ignore-scripts in CI removes the pre-import execution path for everything that does not genuinely need it.
  • Reduce what a compromise reaches. Short-lived cloud credentials via OIDC instead of long-lived keys in repository secrets; least privilege on pipeline tokens; separate build and deploy. Codecov was severe because CI held everything.
  • Detect fast, then rotate. Assume something will get through. The metric that matters is the time between a malicious package being published and you knowing it is in your tree — and after that, whether you rotate the credentials that were exposed.
  • Update deliberately, not blindly. Automated dependency updates are net positive, but merging them unread reintroduces the problem they were meant to solve.

What a scanner can and cannot do

Worth stating plainly, because "prevents supply chain attacks" is a claim the industry makes loosely and it does not survive contact with the xz-utils timeline.

A scanner cannot prevent a package from being malicious. Publication happens on a registry we do not control, on a schedule the attacker chooses. Nothing installed on your side changes that.

What it can do is stop you shipping it, and shorten the window. That is a narrower claim and a more useful one. Concretely: catch a known-malicious or typosquatted package before it merges; flag a brand-new package that nothing has vouched for; find the credential that leaked while the fix is still cheap; and — the one that matters for the patient attacks — re-check what you already run against what the world learned overnight, so the gap between disclosure and your knowing is hours.

For an attack like xz-utils, that last point is the whole of the defence available to a normal company. The realistic goal is not to be un-attackable. It is to not be the company still running it three weeks after everyone else knew.

What is a software supply chain attack?

A software supply chain attack compromises something you depend on rather than something you built — a package, a build tool or an update that you install voluntarily, so your own automation carries the attacker's code the rest of the way. It is efficient for attackers because compromising one widely used package reaches every company that depends on it, and it is quiet because a payload that broke the build would usually be found and removed the same day.

What are the main types of supply chain attack?

Five routes account for most supply chain attacks. An attacker publishes something malicious and waits for a mistake, which covers typosquatting, slopsquatting and dependency confusion. They take over the account of a package you already trust. They spend months becoming a legitimate maintainer, as happened with xz-utils. They compromise the build system rather than the source, as with SolarWinds and Codecov. Or the maintainer themselves turns, as with node-ipc. The first is by far the most common; the third is the hardest to detect.

Can a scanner prevent a supply chain attack?

No. A scanner cannot prevent a supply chain attack, and it is worth being precise about why. Publication happens on a registry nobody outside it controls, on a schedule the attacker chooses, so nothing installed on your side prevents a package from being malicious. What a scanner can do is stop you shipping it and shorten the window: catch a known-malicious or typosquatted package before it merges, flag a package too new to have a history, and re-check what you already run against advisories published overnight, so the gap between disclosure and your knowing is hours rather than weeks.

How do I protect my project from supply chain attacks?

Start by being able to enumerate your dependencies, since every other defence depends on it. Commit a lockfile, so exact versions are pinned in version control and a malicious new release cannot arrive without someone approving a diff. Disable install scripts in CI for packages that do not need them. Reduce what a compromise can reach by using short-lived cloud credentials instead of long-lived keys in repository secrets. Then assume something will get through, and measure how quickly you would know.

What did the xz-utils backdoor teach us?

The xz-utils backdoor showed that the most serious supply chain attacks do not look like attacks while they are happening. An account spent roughly two years making genuine contributions to a compression library, earned co-maintainer status, and then introduced a backdoor into the release tarballs rather than the source repository. No dependency scanner would have caught it on the day it shipped, because the code sat in a trusted package from a trusted maintainer with no advisory against it. It was found by an engineer investigating a half-second delay in a login benchmark. The defence available to a normal company is not prevention but speed: re-checking what you run against what the world learned overnight.

Where CodeControl fits

CodeControl connects to your GitHub and works the detection half of the list above. It resolves your full dependency tree from lockfiles, matches it against the malicious-package feed and published advisories, and flags typosquatted, dependency-confused and brand-new packages that nothing has vouched for yet. It scans within a minute of a push and checks every pull request for committed credentials, so a leak is caught while the fix is still a branch rewrite. And it re-checks your existing inventory every night against newly published exploitation data — which is the only defence a normal company has against the patient attacks.

It will not stop a package from being malicious, and the technical description is explicit about what else it cannot see. What it changes is how long you run it without knowing.