On October 22, 2021, the U.S. Cybersecurity and Infrastructure Security Agency published an advisory naming three npm releases as malicious: ua-parser-js versions 0.7.29, 0.8.0, and 1.0.0. The package, used to detect browser and device type from User-Agent strings, was pulling roughly 8 million weekly downloads at the time, with dependents that reportedly included Microsoft, Amazon, Facebook, Apple, and Oracle. The malicious code was live for only about four hours — from roughly 12:15 to 16:20 GMT — before maintainer Faisal Salman flagged the compromise and npm pulled the versions, with patches shipping as 0.7.30, 0.8.1, and 1.0.1. In that short window, a preinstall script quietly branched by operating system: Linux hosts got an XMRig cryptominer, and Windows hosts got a miner plus a credential-stealing trojan that multiple researchers linked to the DanaBot malware family. No malicious commit ever touched the public GitHub repository. This case is a clean, verified illustration of a build-pipeline compromise that source-code review and CI could never have seen — because the attacker never went near the source. Here is how it worked, and what actually stops it.
What made this different from a typical malicious pull request?
It bypassed source control entirely. The attacker didn't submit a pull request, didn't compromise a GitHub Action, and didn't get commit access to the repository — they got the maintainer's npm publish credentials, most likely through a phished or reused password, and pushed three versions directly to the registry that never existed as commits anywhere. Rapid7 and Kaspersky both documented that the GitHub source tree for ua-parser-js stayed clean throughout the incident. This detail matters more than it sounds: static analysis, PR review, and even a well-configured GitHub Actions pipeline only inspect what's in the repository. If the artifact npm serves to npm install was never built from that reviewed source, none of those controls ever see the malicious code. The registry, not the repo, was the attack surface.
How did the payload actually work across platforms?
Through a preinstall npm lifecycle script — code that executes automatically the moment npm install resolves the package, with no user action beyond running the install itself. The script fingerprinted the host OS at install time and diverged: Linux machines received an XMRig-based cryptominer that quietly consumed CPU cycles for the attacker's benefit, while Windows machines received both the miner and a second-stage payload that CrowdStrike and other outlets reported as consistent with DanaBot, a modular remote-access and banking-trojan family sold as crimeware-as-a-service. macOS was not targeted. Reported behavior included harvesting browser-stored passwords and cookies, and credentials for FTP, email, VPN, and messaging clients, exfiltrated to attacker-controlled infrastructure. That DanaBot attribution comes from secondary security-vendor reporting rather than a formal CVE or the maintainer's own confirmation, but the OS-branching preinstall mechanism itself is confirmed across CISA, Rapid7, and Kaspersky's independent writeups.
Why didn't code review or CI catch it?
Because there was nothing in either to catch. Code review operates on commits and pull requests; this attacker published directly to the npm registry using a valid, stolen authentication token, which is functionally indistinguishable from a legitimate maintainer release unless something checks whether the published artifact actually corresponds to a specific, auditable build. Traditional CI pipelines have the same blind spot in reverse: they scan and test what runs inside the pipeline, but they have no way to know whether the artifact a user eventually installs is the one that pipeline actually produced, if the maintainer can also publish by hand from a laptop. This is precisely the gap that build provenance was designed to close — until a registry can distinguish "built by CI from commit X" from "uploaded from somewhere," a stolen publish token is a complete bypass of every review gate a team has configured upstream.
What is build provenance, and would it have caught this?
Build provenance is a signed, verifiable record binding a published artifact to the exact commit, build system, and build steps that produced it — and yes, it directly addresses this attack class. Under the SLSA framework, a Level 3 provenance attestation requires the artifact to originate from an isolated, parameterless build service rather than an arbitrary developer machine; Safeguard's attestation pipeline implements this using Sigstore's keyless signing, where short-lived certificates are bound to a CI workload identity (GitHub OIDC, GitLab OIDC, etc.) and every signature is logged to the public Rekor transparency log. A publish made from a maintainer's laptop with a stolen npm token — as happened here — produces no valid SLSA provenance at all, because it never ran through the attested CI workflow. A verification check like safeguard verify --artifact <pkg> --expect-slsa-level 3 against such a package fails immediately, and Safeguard's Kubernetes admission controller — which requires SLSA Level 3 by default — denies admission on that failure; the same check wired into a CI gate blocks the pipeline before the artifact ever reaches production. Just as importantly, if an attacker somehow did sign with a different identity, that creates an additional, anomalous Rekor entry Safeguard flags rather than a silent, trusted release.
What catches the payload itself, independent of provenance?
Behavioral detection on the install script, which doesn't require provenance to already be in place. Safeguard's Eagle classification model scores every npm publish — including retroactively across the last decade of registry history — against indicator classes that map directly onto what happened here: install-script behavior that contacts unusual hosts or writes outside the package directory, egress patterns matching known command-and-control infrastructure, and credential-harvesting behavior targeting browser profiles and local credential stores. A preinstall hook that fingerprints the OS and only then reaches out to new infrastructure is exactly the "behavior divergence from prior versions" signal Eagle is built to flag, and a malware-policy gate configured with block: [malicious] at install time stops the install before the script ever executes, independent of whether the publish carried valid provenance.
What should teams actually change after a case like this?
Treat the publish credential, not just the source repository, as the thing under attack. Concretely: enforce npm's built-in provenance feature (or an equivalent SLSA-attested pipeline) so registry consumers can verify an artifact traces to a specific CI run, require phishing-resistant MFA and short-lived, scoped tokens for anyone with publish rights rather than long-lived personal access tokens, and add install-time behavioral scanning so a compromised publish is caught by what it does, not just by whether it was reviewed. UAParser.js was a four-hour window that touched millions of installs; the defense that actually closes that window is the one that doesn't depend on a human noticing in time.
Sources: CISA — Malware Discovered in Popular NPM Package, ua-parser-js, Truesec — The Supply Chain Attack of UAParser.js npm Package, Kaspersky — UAParser.js package infected with password stealer and miner