A supply chain attack poisons something your app already trusts, usually a package you install, so the attacker's code arrives through your own npm install or pip install. Modern apps are mostly other people's code, so an attacker has plenty of trusted packages to poison and never needs to touch your code at all.
What your supply chain is
Open the package.json or requirements.txt of almost any project and you will find a handful of direct dependencies. Install them and you get far more: each package pulls in its own dependencies, and those pull in theirs. A small web app can easily end up with hundreds of packages in node_modules, written by people you have never heard of.
That whole tree is your software supply chain. It also includes the tools that fetch and build the code:
- the registries packages come from (npm, PyPI, NuGet);
- the package managers that download and install them;
- the build and CI systems that run your installs and publish your releases;
- the accounts and tokens maintainers use to publish new versions.
You trust every one of these by default. When you run an install, the package manager downloads whatever the registry says is the right version and, in many ecosystems, runs its install scripts straight away. Nobody reads the code first. An attacker who gets malicious code into any part of that chain can run it on your laptop, your CI server or your users' browsers.
Four ways in
The video named four routes into a dependency tree.
Fake packages with similar names
This is also called typosquatting. The attacker publishes a package whose name is one slip away from a popular one: a swapped letter, a missing hyphen, a plural. Anyone who mistypes the name in an install command, or copies it from a bad tutorial, gets the fake. Many copy the real package's code and README, then add a few lines that steal environment variables on install.
Dependency confusion
Many companies keep private packages on an internal registry, with names like acme-auth. If the package manager is set up to look in both the internal registry and the public one, an attacker can publish a public package with the same name and a higher version number. Some setups then pick the public one, because it looks newer. In 2021 a security researcher used this trick, with harmless code, to get his packages installed inside dozens of large companies, including Apple, Microsoft and PayPal.
Hijacked libraries
Here the package is real, and so is its name. The attacker takes over the maintainer's account, usually by phishing, and publishes a new version with malicious code added. Everyone whose version range allows the new release picks it up on their next install. This route does the most damage, because the package has years of trust and millions of downloads behind it.
Leaked secrets
Publishing a package needs a token. So does pushing to a repository or deploying to the cloud. A token committed to a public repository, printed in CI logs or stolen from a developer's machine lets an attacker publish as you. Leaked secrets are often how a hijack starts, and stealing more of them is often what the malicious code does next.
How a hijacked package reaches your app
The maintainer of a popular package gets an email that looks like it comes from the registry, asking them to update their two-factor settings. The link goes to a copy of the login page. They sign in, and the attacker now has a working session or token.
The attacker publishes a new patch version, say 1.2.1, that adds a short install script. Your project depends on ^1.2.0, which allows any 1.x version from 1.2.0 up, so your next fresh install pulls it in. The script runs during the install, reads the environment variables and config files it can find (npm tokens, cloud keys, GitHub tokens) and sends them to a server the attacker controls.
If your stolen secrets include an npm token, the attacker can then publish poisoned versions of your packages, and everyone who depends on them is next in line. That is how one hijack turns into hundreds.
Here is that chain, one step at a time:
How one hijacked package spreads
Step 1 of 7: The attacker sends the maintainer a fake 'registry support' email.
This isn't rare
The video mentioned four real attacks, all from 2025, all hidden inside trusted packages:
- XRPL: in April, attackers published versions of
xrpl, the official JavaScript library for the XRP Ledger, with a backdoor that sent wallet seeds and private keys to a newly registered domain. The poisoned versions had no matching release on the project's GitHub. - s1ngularity: in August, malicious versions of the Nx build tool ran a script on install that hunted for credentials, even asking AI coding assistants on the machine to help search, and uploaded what it found to public GitHub repositories.
- debug and chalk: in September, a maintainer was phished through a lookalike npm support domain. The attacker published poisoned versions of 18 packages, including
debugandchalk, with over 2 billion weekly downloads between them. The code ran in website visitors' browsers and swapped crypto wallet addresses for the attacker's. - Shai-Hulud: a week later, a self-spreading worm infected hundreds of npm packages. It stole developer tokens and cloud keys, then used any npm token it found to publish infected versions of that maintainer's other packages: the same loop as the hijack described earlier.
The debug and chalk versions were spotted and pulled within hours, which was still long enough for automated builds around the world to install them.
Why they're hard to spot
A vulnerability is a mistake in code, and scanners find known ones by matching versions against a database of reported problems. In a supply chain attack the code is malicious on purpose, and brand new. There is no advisory for it yet, so a tool that only checks for known vulnerabilities sees nothing wrong.
The code is also written to blend in. It is often obfuscated and tucked into a minified file or an install script nobody opens, while the package keeps doing its normal job. Your tests pass and nothing errors. You find out when someone else spots the new version, or when your keys start being used.
How to protect your projects
No single step stops every route, but a few habits close most of them.
Pin what you install. Commit your lockfile and install from it in CI with npm ci (or pnpm install --frozen-lockfile). Your builds then get exactly the versions you reviewed, not whatever was published this morning.
Wait before you upgrade. Most poisoned versions are caught within hours or days. Holding new releases back for a day or two before installing them avoids most of that window. pnpm has a minimumReleaseAge setting for this.
Skip install scripts you don't need. Many attacks run from a postinstall script. pnpm 10 no longer runs dependencies' install scripts unless you allow them, and npm can skip them:
npm ci --ignore-scriptsSome packages need their scripts to build native code, so allow those by name.
Tie internal names to your own registry. Against dependency confusion, publish internal packages under a scope you own and point that scope at your private registry in .npmrc:
@acme:registry=https://npm.acme.internal/Check each package as it arrives. Aikido's Safe Chain, the tool in the video, does this. It runs on your laptop and wraps the usual commands (npm, pnpm, yarn, pip, uv and others). Every install goes through a small local proxy that checks each package, including deep dependencies, against a live list of known malware. If a package is on it, Safe Chain blocks the download before it reaches your project. It can also refuse versions younger than a set age, and run the same checks in CI.
Protect your own publishing. Turn on two-factor authentication for your registry and GitHub accounts, ideally with a passkey or security key that a fake login page can't phish. Give CI the narrowest, shortest-lived tokens it can work with. If a poisoned package ever ran on a machine, rotate every secret that machine could read.
For a wider view of building security into your pipeline, see DevSecOps.
Common mistakes
- Trusting popularity:
chalkanddebugare among npm's most downloaded packages, which is exactly why attackers went after them. - Only checking direct dependencies: most of your tree is indirect, and an attack anywhere in it runs just the same. See Dependency Hell for how deep that tree goes.
- Treating
npm auditas a malware scanner: it lists problems that have already been reported, and a version published an hour ago usually has no advisory yet. - Removing the bad package and stopping there: if it ran, assume your secrets left with it, and rotate them.
- Running
npm installin CI: it can resolve newer versions within your ranges, whilenpm ciinstalls exactly what the lockfile says.
For more on packages built to be malicious from day one, see Malicious Packages.
Key takeaways
- A supply chain attack poisons something you trust, usually a dependency, instead of attacking your app head-on.
- The main routes are lookalike names, dependency confusion, hijacked maintainer accounts and leaked secrets.
- Poisoned code runs with your permissions and causes no obvious errors, so vulnerability scanners alone won't catch it.
- Lockfiles, a short wait before upgrading, fewer install scripts and a check on every install close most of the gaps.
- If a bad package ran, rotate every secret it could reach.