Safeguard
Threat Research

Lessons from the ua-parser-js Compromise: Four Hours, Eight Million Downloads a Week

A hijacked npm account turned a tiny User-Agent parser into a cryptominer and password stealer for a few hours in 2021. Here is what account takeover does at ecosystem scale.

Marcus Chen
Security Researcher
6 min read

On October 22, 2021, an attacker who had hijacked a maintainer's npm account published malicious versions of ua-parser-js, a tiny library that parses browser User-Agent strings and is downloaded around 8 million times a week. For a few hours, anyone installing the poisoned versions got a cryptominer and a password stealer. It is a compact, brutal lesson in what account takeover of a popular maintainer can do at ecosystem scale.

What happened: a timeline

ua-parser-js is maintained by Faisal Salman and is depended on, directly or transitively, by a vast number of applications and other packages. On October 22, 2021, Salman's npm account was hijacked — he later reported that his email was suddenly flooded with spam, a common sign of an account takeover in progress. The attacker published three malicious versions: 0.7.29, 0.8.0, and 1.0.0. The bad versions were live for roughly four hours before being pulled. CISA issued an advisory the same day urging users to check for the compromised versions.

How the attack worked

The malicious versions carried a preinstall script that ran automatically when the package was installed — no application code even had to import the library. The script checked the operating system and branched: on Linux it ran a shell script, on Windows a batch file.

On Linux, it first checked whether the machine was located in Russia, Ukraine, Belarus, or Kazakhstan and skipped those — a common tell of the operator's origin. Otherwise it downloaded and ran an XMRig Monero cryptominer, saved as jsextension, throttled to about half the CPU to stay quiet. On Windows, it dropped the same miner as jsextension.exe and additionally deployed a credential stealer that harvested passwords from a range of local applications. Install the package, and your machine started mining cryptocurrency for someone else and, on Windows, leaking saved passwords.

Impact

With around 8 million weekly downloads and heavy transitive use, the potential exposure was massive even in a four-hour window — automated CI pipelines and developer machines pull these packages constantly. Because the payload ran at install time and stole credentials, affected users were advised not merely to downgrade but to treat the host as compromised: remove the malware, rotate all credentials and secrets that may have been present, and rebuild if necessary.

The concrete lessons

Maintainer account security is ecosystem security. A single hijacked npm account weaponized a library on 8 million machines a week. Mandatory two-factor authentication on publish accounts is one of the highest-leverage controls in open source.

Install scripts are code execution before you use the library. The payload never needed to be imported; the preinstall hook ran on npm install. Lifecycle scripts deserve as much scrutiny as the library's actual API.

Speed of detection and response is everything. The window was hours, not months — but automation means hours is plenty. You need to know fast whether a poisoned version entered your builds.

Pinning and lockfiles limit the window. Teams that pinned exact versions and did not auto-adopt the latest release were less likely to pull the malicious versions during their brief availability.

How a platform like Safeguard would have helped

Straight talk first: in the first minutes after 0.7.29 went live, it was unknown-malicious, and no database yet flagged it. Blocking it on the very first install is a genuinely hard problem for any tool. What made ua-parser-js survivable is that it was identified as malicious quickly and pulled within hours.

That is exactly the window where a platform like Safeguard matters. Once versions 0.7.29, 0.8.0, and 1.0.0 are flagged as malicious, Safeguard's software composition analysis resolves your dependency graph — including the many transitive paths that pull in a utility like this — and surfaces whether any build consumed a poisoned version, elevating a known-malicious package above routine CVE noise because it represents active, install-time code execution. Automated fix pull requests then pin you back to a safe version across affected repositories rather than leaving a manual sweep, and Griffin AI helps triage which projects and machines actually pulled the bad versions and what the response should be. Because CI images routinely install npm packages during the build, container image scanning helps confirm whether a poisoned version was baked into an artifact.

The honest boundary: no tool would have let you install ua-parser-js 0.7.29 safely in the first hour. What tooling provides is the ability to answer "were we hit, and where" in minutes, and to remediate at the speed the attack demanded — which, in a four-hour incident, is the whole ballgame.

Frequently Asked Questions

Do I need to do more than downgrade after installing a compromised version? Yes. Because the malicious versions executed a cryptominer and, on Windows, a password stealer at install time, security guidance was to treat any affected machine as compromised: remove the malware, rotate every credential and secret that may have been present, and rebuild the system if warranted. Downgrading the package alone does not undo code that already ran.

How did the attacker publish malicious versions of ua-parser-js? They hijacked the maintainer's npm account. The maintainer reported a sudden flood of spam email — often a smokescreen for a password reset or account-takeover attempt — after which three malicious versions were published under his account. This is why two-factor authentication on maintainer accounts is so important.

Why did the malware avoid certain countries? The Linux payload checked whether the host was located in Russia, Ukraine, Belarus, or Kazakhstan and skipped execution there. Geofencing out particular regions is a common characteristic that many analysts read as a hint about where an operator is based or seeking to avoid attention.

How can a package be dangerous if I never import it? Because npm lifecycle scripts like preinstall run automatically during npm install, before and regardless of whether your code ever requires the package. The ua-parser-js payload executed entirely through that install hook, which is why install-time scripts are a favored malware delivery mechanism.

Get started at app.safeguard.sh/register, and find integration guides at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.