Safeguard
Incident Analysis

UAParser.js npm package compromise

A deep dive into the 2021 ua-parser-js npm compromise: how a hijacked maintainer account delivered cryptominers and credential stealers to millions.

Nayan Dey
Security Researcher
7 min read

Incident Analysis — npm Ecosystem. On October 22, 2021, at approximately 4:30 AM UTC, an attacker with control of a compromised npm maintainer account published three malicious versions of ua-parser-js — 0.7.29, 0.8.0, and 1.0.0 — to the npm registry. The package, a lightweight user-agent string parser embedded in countless web analytics, telemetry, and framework tooling stacks, was pulling roughly 7 to 8 million weekly downloads at the time, with dependents reportedly including infrastructure tied to Amazon, Microsoft Azure, and Facebook's build tooling. Within about four hours, npm's security team had pulled the compromised versions and GitHub had issued a security advisory, but the exposure window was long enough for automated CI pipelines and unattended npm install runs worldwide to pull down a cryptominer, a credential-stealing trojan, or both — without a single line of application code changing hands.

The ua-parser-js incident has become one of the reference case studies for npm account-takeover attacks, not because it was the most damaging supply chain compromise of its era, but because it was so mechanically clean: a trusted, widely-embedded, low-visibility dependency; a hijacked maintainer credential with no additional friction; and a payload that activated silently through the most routine action in modern software delivery — installing dependencies.

What Happened

The maintainer of ua-parser-js, Faisal Salman, confirmed publicly that his npm account had been compromised, most likely through a reused or otherwise exposed password, and that the account did not have two-factor authentication enabled. The attacker used that access to publish three new versions directly to the registry — bypassing the package's GitHub repository entirely, since none of the malicious commits ever appeared in source control. That detail matters: any team relying on reviewing GitHub diffs, watching commit activity, or trusting "the repo looks clean" as a signal would have found nothing wrong, because the compromise lived only in the published npm tarball.

Each malicious version shipped a preinstall script that fingerprinted the host operating system and dropped a platform-specific second-stage payload:

  • On Linux, the script silently launched an XMRig-based cryptocurrency miner (delivered as a binary commonly referenced as jsextension), consuming CPU cycles on build servers, CI runners, and containerized workloads with no visible symptom beyond degraded performance.
  • On Windows, the payload was materially worse: a binary (create2.exe) that installed a credential-stealing trojan capable of harvesting saved passwords, browser session data, and other locally stored secrets, then exfiltrating them off-host.

Because the payload executed at install time via a lifecycle script, it required no application runtime trigger, no user interaction, and no outbound API call from the vulnerable package's actual parsing logic. Anyone — or any pipeline — that ran npm install or npm update against a project depending on ua-parser-js during that window was a candidate victim, regardless of whether the application ever executed the parser at all.

Why the Blast Radius Was So Large

ua-parser-js is the textbook definition of a "invisible" transitive dependency: a small, single-purpose utility pulled in by larger frameworks and tooling rather than installed deliberately by application developers. Most engineering teams whose builds were affected had never heard of the package and had no direct line of sight into why it was present in their node_modules tree. This is precisely the profile that makes npm supply chain attacks so effective — the compromised artifact sits several layers below the code a team actually reviews, and standard dependency update workflows (patch-level auto-updates, lockfile-less installs, ^ version ranges) are specifically designed to pull in exactly this kind of change without a human ever looking at it.

The incident also illustrates a structural weak point in registry trust models that has recurred across multiple ecosystems since: package publishing authority is frequently gated by nothing stronger than a password. No hardware key, no mandatory 2FA, no build provenance attestation tying a published artifact back to a specific, reviewed commit. When that authority is compromised, the registry itself becomes the delivery mechanism, and the "malicious code review" step that defenders assume exists simply never happens.

Detection and Response Timeline

npm's registry security team and the broader open-source community moved quickly once the anomalous package versions were flagged — reports point to detection and takedown within roughly four hours of publication, a genuinely fast turnaround for a volunteer-and-vendor-staffed ecosystem. GitHub issued a security advisory (tracked publicly as GHSA-mr8g-8v8w-9pgr) flagging the affected version range, and Salman published remediated releases (0.7.30 and 1.0.1) shortly after regaining control of the account. For most organizations, the practical remediation was straightforward once identified: pin dependencies away from the compromised versions, rotate any credentials that may have touched an affected Windows host, and audit CI runners for unexpected mining processes or outbound connections.

The harder problem — and the one that persists industry-wide — is the detection step that has to happen before remediation can start. Four hours is fast for a registry-level response, but it is not fast relative to how quickly automated CI/CD pipelines re-resolve and install dependencies. Any organization running frequent, unattended builds during that window had no realistic way to catch the compromise through manual review; it required either registry-side intervention or automated monitoring of dependency changes and runtime behavior.

Part of a Recognizable Pattern

ua-parser-js did not happen in isolation. Within the same general period, the coa and rc packages were compromised through nearly identical account-takeover mechanics, and the years since have produced a steady cadence of comparable npm and PyPI incidents — typosquatting campaigns, maintainer account phishing, and malicious lifecycle scripts embedded in otherwise legitimate, high-download packages. The common thread across nearly all of them is that the malicious payload rides in through the install-time execution surface of package managers rather than through application logic that a security team would normally scan for. Static analysis of "what does this package's exported function do" misses the threat entirely; the danger is in what runs the moment the package lands on disk.

For defenders, the actionable lesson from ua-parser-js is not "audit every dependency by hand" — that doesn't scale past a handful of direct dependencies, let alone the thousands of transitive packages in a modern lockfile. The lesson is that supply chain defense requires continuous, automated visibility into what actually changed in a dependency between versions, whether that dependency is genuinely reachable from production code paths, and what a newly resolved package version does at install and runtime — independent of whether its source repository looks trustworthy.

How Safeguard Helps

Safeguard is built around the assumption that incidents like ua-parser-js are not edge cases — they are the recurring failure mode of registry-based package trust, and defenses need to operate at the speed of npm install, not the speed of manual review. Our SBOM generation and ingest pipeline continuously maps every direct and transitive dependency across your codebase, so a compromised package like ua-parser-js is identified the moment a malicious version enters your build, not weeks later during an audit. Reachability analysis then determines whether the vulnerable or malicious code path is actually exercised by your application, letting security teams triage a supply chain alert in minutes instead of chasing every dependency with equal urgency. Griffin AI, Safeguard's autonomous security analyst, correlates advisory data, package behavior signals, and your specific dependency graph to flag anomalous publishes — like an unreviewed lifecycle script or a version jump with no corresponding source commit — before they propagate through CI. When remediation is warranted, Safeguard's auto-fix PRs pin affected packages to known-safe versions and open a reviewed pull request automatically, closing the gap between detection and remediation without waiting on a manual dependency bump.

Never miss an update

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