Supply Chain Attacks: From npm Typosquatting to Poisoned Container Images

The threat that arrives through your own build pipeline

The most effective way to attack a company isn’t to break down its front door. It’s to be standing inside the delivery van that the company waves through the gate every morning. Supply chain attacks work because they exploit the one thing every developer does without thinking: pull in code and images they didn’t write, from people they’ve never met, and run them with full trust.

You did it this morning. npm install. docker pull. pip install. Each one is an act of faith that the thing at the other end is what it claims to be and hasn’t been tampered with since you last looked. Mostly that faith is rewarded. The supply chain attacker’s entire business is the times it isn’t.

Advertisement

The simplest attack barely deserves the name “attack.” Package registries like npm and PyPI let anyone publish almost any name. So an attacker publishes a package called electorn (note the typo) hoping that someone, somewhere, in a hurry, will fumble electron. Or they register python-requests betting you’ll grab it instead of requests. The malicious package does whatever the real one does — so nothing seems wrong — while also quietly exfiltrating your environment variables, which on a CI runner means your cloud credentials and registry tokens.

This isn’t hypothetical. Registries pull down dozens of typosquatted packages every month. The defence is unglamorous and effective: pin your dependencies and read the names. A lockfile commits you to exact versions and exact resolved sources:

# don't just `npm install` in CI — install exactly what the lockfile says:
npm ci          # fails if package.json and lock disagree; no surprise upgrades

npm ci (and pip install --require-hashes, and friends) turns “grab whatever’s newest” into “grab precisely the bytes I reviewed.” That one change closes the door on a whole category of drive-by poisoning.

A nastier variant exploits how tooling resolves package names. If your company uses an internal package called acme-utils that lives only in your private registry, an attacker can publish a public package with the same name and a higher version number. Misconfigured tooling, asked for acme-utils, sees the public one is “newer” and pulls the attacker’s code instead of yours. Researchers used exactly this trick to land code inside the build systems of some very large companies, simply by guessing internal package names.

The fix is to be explicit about where a name is allowed to come from — scoping internal packages and locking the resolver to the right registry so a public impostor can never win the version race.

Containers raise the stakes because an image isn’t one dependency — it’s a frozen filesystem full of them, plus a base image, plus whatever the maintainer baked in. The attacks rhyme with the npm ones:

  • Typosquatted images on public registries, banking on nginix instead of nginx.
  • Tag mutationlatest is not a contract. The image you pulled last week and the one you pull today can be completely different bytes under the same tag, and nothing warns you.
  • Compromised upstreams — a maintainer’s credentials get phished and a popular image quietly gains a backdoored layer.

The single most important habit here is to pin by digest, not by tag. A tag is a sticky note someone can move; a digest is a cryptographic hash of the exact image:

# fragile: whatever 'latest' points at today
FROM node:latest

# pinned: this and only this image, forever
FROM node:20-slim@sha256:9d0e0d8e8f...c2a1

That @sha256:... means your build gets bit-for-bit the image you vetted, or it fails loudly. No silent substitution.

The frontier of supply chain defence is provenance — being able to prove an artefact was built from the source you think it was, by the pipeline you think built it. This is where SBOMs (Software Bills of Materials) and signing come in. An SBOM is just a manifest of everything inside an artefact; tooling can generate one and later answer “am I affected by the new CVE in libfoo?” without you guessing. Signing — with tools like Sigil-style cosign signatures and keyless OIDC-based attestations — lets you verify that an image was produced by your CI and not swapped out in the registry:

# verify an image was signed by the expected workflow, or refuse to deploy it
cosign verify --certificate-identity-regexp '.*github.com/myorg/.*' myorg/app:1.4.2

The point isn’t any single tool. It’s the shift in posture: from “I trust this because it has the right name” to “I trust this because I can verify where it came from.”

You cannot audit every line of every dependency you pull — nobody can, and pretending otherwise is how people get paralysed. What you can do is make tampering loud instead of silent: lockfiles and npm ci so you get exactly what you reviewed, digest-pinned base images so tags can’t betray you, scoped internal packages so confusion attacks fail, and signing plus SBOMs so you can prove provenance and answer the “are we affected” question in minutes rather than days.

Who is this for? Anyone with a build pipeline — which is anyone who ships software. The supply chain attack doesn’t care how good your firewall is, because it walks in through the front gate you hold open every morning. The least you can do is check the van.

Advertisement

Related Content

Advertisement
Smarc
Written by Smarc

Founder and editor of vo.rs. A lifelong tinkerer who self-hosts far more than is sensible, hardens Linux boxes for fun, and prods the latest AI tools to see what they can really do. The how-to guides here are the notes Smarc wishes had existed the first time round.