npm install is the dumbest little ritual we keep pretending is normal.
We type one command, wave our hands at the screen, and ask a stranger’s code to move into the house. Then we act shocked when the stranger starts counting the silverware.
Microsoft’s latest report on malicious npm packages is a perfect little horror story: 33 packages, three maintainer aliases, nine fake internal scopes, two publishing bursts, and one shared command-and-control setup. The payload isn’t even trying to be a cinematic supervillain. It’s doing something much uglier and much smarter: profiling developer machines.
That’s the part people keep missing. This isn’t just “malware.” It’s target selection.
The packages masquerade as internal tooling with names that sound like something a corporate wiki would cough up in the middle of a compliance coma: ssh-keys, enterprise, monitoring, add_application_service_token, and friends. They spoof internal-looking repository, docs, and bug tracker URLs. They crank version numbers into absurdity — 100.100.100, because apparently the public registry is still willing to reward the loudest liar in the room. And then they drop a postinstall hook that runs automatically because the ecosystem still treats install-time code execution like a cute convenience feature instead of a loaded gun on the table.
The payload itself is mostly reconnaissance. It fingerprints hostnames, environment variables, installed tools, and whatever else fits inside the phrase “developer context,” which is a wonderfully polite way to say “everything an attacker wants before they come back for the good stuff.” It even checks whether it’s running in CI and politely backs off, because the whole point is to stay quiet in monitored environments and snatch the local laptops instead.
And yes, the report says the campaign runs in RECON_ONLY=1 mode by default.
That detail should make everyone uncomfortable. It means the malware is already designed as a two-stage operation. First it maps the room. Then, if the operator flips the switch server-side, it can go from nosy asshole to full-on burglar. That is not a bug. That is the business plan.
This is why “dependency confusion” is such a cowardly little phrase. Confusion implies accident. This is impersonation. It’s package-manager cosplay. It’s someone walking into your supply chain wearing a fake badge and a better semver number, and the registry just nods and says sure, whatever, go ahead.
The really depressing part is that this works because modern package tooling still trusts too much by default. If a package name looks plausible, the registry resolves it. If the package wants to run code during install, the ecosystem shrugs and calls it developer experience. If the package pretends to be internal and publishes a prettier version than your real one, congratulations, your defenses just got outbid by a clown with a keyboard.
So no, I do not find this sophisticated in the romantic sense. It’s not elegant. It’s not clever in a way worth admiring. It’s just disciplined, scalable nastiness aimed at the softest part of the software stack: the place where humans still believe packages are mostly honest.
If your org still lets arbitrary lifecycle scripts run everywhere, you are not “moving fast.” You are letting strangers ring the doorbell and then handing them the house keys because the README looked clean. At minimum: lock private scopes to private registries, stop blindly trusting preinstall and postinstall, pin versions like you actually mean it, and treat every package that wants to execute on arrival as suspicious until proven boring.
And if that sounds annoying, good. Safety is supposed to be annoying. The alternative is this: your package manager becomes a free fingerprinting service, your developer laptops become reconnaissance targets, and the only thing the attacker had to do was wear a fake namespace and wait for npm install to do the rest.
Which, frankly, is a pretty embarrassing way to lose.