How a typosquatted npm package ships a Windows RAT through a single install
A package named postcss-minify-selector-parser borrows the keyword space of a library that does over 127 million weekly downloads, then uses the npm install lifecycle to decrypt a dropper, write a PowerShell stager, and land a Windows remote access trojan. The cipher is fine. The trust model is the bug.
Researchers disclosed a small cluster of malicious npm packages published over the past month under a single account, including aes-decode-runner-pro, postcss-minify-selector, and postcss-minify-selector-parser. Download counts were modest, in the low hundreds each, but the targeting is precise: front-end and build-tooling developers on Windows. This is a textbook combination of two bug classes, dependency-name impersonation and install-time script abuse, stitched into one chain.
The name is the lure
The legitimate postcss-selector-parser is a transitive dependency of much of the PostCSS ecosystem. The impostor reuses the same tokens, postcss, selector, parser, css, so it surfaces in registry search and reads as plausible during a quick dependency review. It even declares the real package as a dependency, so installing the fake also pulls the genuine one and the install log looks ordinary. Typosquatting works because dependency names are matched by humans skimming, not by signatures.
A name that is one keyword away from 127 million weekly downloads is not a coincidence. It is the payload's first stage. recurring pattern across npm, PyPI, and RubyGems
The chain, stage by stage
The interesting part runs at install time, before any of your code imports the module. The package carried a large encrypted blob alongside an AES-256-GCM decoder. On install the decoder unpacked the blob in memory, and the result acted as a dropper: it wrote a PowerShell script to disk and executed it. That script reached out to a domain dressed up as a driver site, nvidiadriver[.]net, and pulled the next stage.
The final payload is a full Windows RAT. Reporting describes encrypted HTTP command-and-control, registry-based persistence, sandbox and VM detection via WMI and MAC-address checks against VMware, VirtualBox, and QEMU, a remote shell, file transfer, and Chrome credential theft through DPAPI. The staging keeps each piece small and defers the heavy logic to a server the attacker controls, a pattern sometimes called remote dynamic dependencies: the package itself looks thin, and the real code arrives only at install.
{
"name": "postcss-minify-selector-parser",
"version": "1.0.3",
"dependencies": {
"postcss-selector-parser": "^6.0.0" # pulls the real one, for cover
},
"scripts": {
"postinstall": "node ./lib/runner.js" # decrypts blob, drops + runs PS1
}
}
The lesson generalizes. A postinstall or preinstall hook that runs a local script, spawns a shell, or fetches a remote resource is executing arbitrary code on the developer's machine with the developer's privileges and tokens. The same shape recurs in update-channel compromises like the ShapedPlugin backdoor: a trusted automated step becomes the delivery mechanism.
Detecting it in a lab
Reproduce the recognition skill, not the malware. Spin up a disposable Windows VM with no real credentials, then practice spotting the markers before you ever run an install.
- Read
package.jsonfirst: anypreinstall,install, orpostinstallthat callsnode,powershell,curl, or a bare script is a flag. - Diff the package against the name it mimics. A real parser library does not ship a multi-megabyte encrypted blob or an AES decoder it never uses at runtime.
- Install with scripts disabled to neutralize the trigger:
npm install --ignore-scripts. Then inspect on disk. - Watch for child processes: an
npm installthat spawnspowershell.exeor reaches an unfamiliar domain is the chain firing.
Defending real projects
Treat dependency installation as a trust boundary, the same way you would an untrusted network request. The crossing here is the moment npm runs someone else's lifecycle script on your host.
- Set
ignore-scripts=truein.npmrcby default and allow lifecycle scripts only for the few packages that genuinely need them. - Pin and review your lockfile. New transitive names appearing in a diff deserve a look, especially ones a token away from a popular package.
- Run a software composition or registry-malware scanner in CI so a freshly published impostor is flagged before it reaches a workstation.
- If you suspect exposure, assume credential theft: rotate developer tokens, cloud and CI/CD secrets, and any browser-stored credentials, then rebuild the host from a clean image.
None of this requires new cryptography or a novel exploit. It is the predictable outcome of a registry that runs publisher-supplied code at install time and a review process that matches names by eye. The defender's habit worth building is to read every install hook as if it were a remote shell, because for one install, that is exactly what it was. For more on trusted layers turning into entry points, see when the perimeter becomes the payload, and the project ethics and disclosure policy for how we handle live-campaign reporting.
Sources
- The Hacker News. "Malicious npm Packages Pose as PostCSS Tools to Deliver Windows RAT." Read the report
- Infosecurity Magazine. "Lookalike npm Package Hides a Multi-Stage Windows RAT." Read the article
- Cyber Security News. "Windows RAT Uses Encrypted HTTP C2 and Registry Persistence After npm Infection." Read the analysis
- npm Docs. "scripts and the package lifecycle (preinstall/postinstall)." Read the documentation