Safeguard
Software Supply Chain Security

ua-parser-js npm hijack incident

In 2021, a hijacked npm account pushed cryptomining and password-stealing malware into ua-parser-js for 4 hours. Here's what happened and how to catch it faster.

Nayan Dey
Security Researcher
6 min read

On October 22, 2021, three versions of ua-parser-js — 0.7.29, 0.8.0, and 1.0.0 — were published to npm carrying a malicious preinstall script. The package, which parses User-Agent strings to detect browser, OS, and device type, sat at roughly 8 million weekly downloads and was a dependency of more than 1,200 projects, including tooling used by Facebook, Microsoft, and Amazon-adjacent stacks. The malicious versions were live on the registry for approximately four hours — from 12:15 PM GMT until between 4:16 and 4:26 PM GMT — before the maintainer, Faisal Salman, pulled them. In that window, the script attempted to drop an XMRig Monero cryptominer on Windows and Linux hosts and, on Windows, a trojan that harvested browser cookies and saved credentials. The root cause was a compromised npm account, not a code vulnerability, which is exactly the class of attack that static scanning alone cannot catch.

What Actually Happened in the ua-parser-js Hijack?

Faisal Salman's npm publishing credentials were compromised, and the attacker used them to push three tainted releases directly to the registry — no pull request, no code review, no GitHub commit history to flag. Versions 0.7.29, 0.8.0, and 1.0.0 each contained a preinstall script that ran automatically the moment npm install executed, before any application code loaded. That script fetched a second-stage payload (create2.js on Windows, create3.js on Linux/macOS) that installed an XMRig cryptominer and, on Windows machines specifically, a password-stealing trojan targeting saved credentials in Chrome, Firefox, email clients, FTP tools, VPN configs, and messaging apps. Because ua-parser-js is a transitive dependency in many toolchains, most affected teams never explicitly listed it in package.json — it arrived through a build tool, a UI library, or an analytics SDK several layers removed.

Why Was This Attack So Dangerous Despite Lasting Only Four Hours?

Because the payload executed at install time, not at runtime, meaning exposure depended on when a machine ran npm install, not on whether the vulnerable code path was ever called. Any developer laptop, CI runner, or Docker build that pulled dependencies between 12:15 PM and roughly 4:20 PM GMT on October 22, 2021 was potentially compromised — even if the application never imported or invoked ua-parser-js in its own code. This is the defining trait of install-time supply chain malware: traditional software composition analysis (SCA) tools that only flag known CVEs after public disclosure offer zero protection during the live attack window, because there is no CVE yet — just a malicious package version sitting on the registry looking identical to a normal release. GitHub's own advisory (GHSA-pjwm-rvh2-c87w) wasn't published until after the fact, which is standard: detection is always retroactive unless something is watching package behavior in real time.

How Many Organizations Were Actually Exposed?

Exposure was limited to whoever ran an install during that roughly four-hour window, not the full 8-million-weekly-download user base. CISA issued an alert on October 22, 2021 the same day, and Rapid7, Snyk, and GitHub all published advisories within 24-48 hours urging teams to check build logs and CI timestamps against the compromise window. Teams that pinned ua-parser-js to an exact prior version, or that used lockfiles without automatic minor-version updates, were not affected regardless of when they built. Teams using loose semver ranges (^0.7.28 or similar) in package.json without a committed lockfile were the highest-risk group, since a fresh npm install during the window would silently resolve to 0.7.29. The maintainer's own remediation — publishing clean versions 0.7.30, 0.8.1, and 1.0.1 within hours — meant the fix was available almost as fast as the attack, but only for teams actively watching for it.

What Made ua-parser-js a High-Value Target for Attackers?

Its ubiquity as an invisible transitive dependency made it valuable — attackers get maximum blast radius from compromising one popular utility package rather than targeting individual applications. ua-parser-js is pulled in by build tooling, browser-detection middleware, and analytics libraries across the JavaScript ecosystem, so a single compromised maintainer account translated into potential access across thousands of downstream codebases without the attacker needing to breach any of them directly. This same pattern repeated within days: the coa and rc npm packages were also hijacked via compromised maintainer accounts in the same week of October 2021, confirming this wasn't an isolated incident but a wave targeting npm account security specifically — likely through credential reuse or phishing, since npm did not require 2FA for publishing at the time.

Could This Have Been Caught Before It Spread?

Yes, but only with controls that inspect package behavior and account activity rather than just matching known CVEs. Static SCA tools that rely solely on public vulnerability databases had nothing to match against during the four-hour window, since the malicious versions weren't yet cataloged as vulnerable. What would have helped: monitoring for anomalous publish events on high-traffic dependencies, blocking preinstall/postinstall script execution by default in CI, and maintaining an SBOM granular enough to show exactly which build artifacts pulled which package version at which timestamp — turning "were we exposed?" from a multi-day forensic exercise into a query. npm's later requirement of 2FA for maintainers of packages with high download counts was a direct response to this incident and the coa/rc compromises that followed it.

How Safeguard Helps

Safeguard closes the exact gap this incident exposed: the time between a malicious package landing on a registry and a team discovering it was ever installed. Continuous SBOM generation and ingest let you query, retroactively, which builds pulled which version of ua-parser-js and when — collapsing a days-long log-forensics exercise into a single search. Griffin AI monitors dependency behavior and publish anomalies across your software supply chain, flagging suspicious releases like install-time script payloads before they're widely known to be malicious rather than waiting for a public CVE. Reachability analysis then tells you whether a flagged package's malicious code path is actually invoked in your build or runtime, so security teams can triage real exposure instead of chasing every transitive match. When a fix is available, Safeguard opens an auto-fix pull request to bump the dependency to a clean version immediately, cutting the remediation window from days to minutes.

Sources:

Never miss an update

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