Three hours on npm: how the Axios compromise played out
On March 31, 2026, axios@1.14.1 shipped with a cross-platform RAT injected through a hijacked maintainer account. The malicious version was live for roughly three hours before npm pulled it. axios is in around 100 million installs per week. Three hours was enough.
TL;DR
On March 31, 2026, between 00:21 and 03:25 UTC, two malicious versions of the axios npm package were live: axios@1.14.1 on the latest dist-tag and axios@0.30.4 on the legacy dist-tag. The malicious versions added a trojanized runtime dependency, plain-crypto-js, which downloaded and executed a cross-platform remote access trojan on install. The attacker had compromised an axios maintainer account; the published versions were signed by that maintainer’s npm token and showed all the right metadata.
The active window was approximately three hours. axios has on the order of 100 million weekly downloads. Three hours is plenty.
What happened
The incident has been written up by several teams; I am cross-referencing Datadog Security Labs, Elastic Security Labs, Snyk, Huntress, and the Malwarebytes summary.
Approximate timeline (UTC):
- March 30, late evening: attacker gains access to an
axiosmaintainer account. Initial vector is still under investigation as of this post; reporting suggests social engineering against the maintainer, but I would not bet on the specifics holding. - March 31, 00:21:
axios@1.14.1published to npm. Versioned as a patch release. The published version includesplain-crypto-js@4.2.1as a runtime dependency (a typosquat of the legitimatecrypto-jslibrary that npm did not flag). - March 31, 00:23:
axios@0.30.4published. This bumps thelegacydist-tag, which is used by older codebases pinned to the 0.x line. - March 31, ~01:30: first automated scanners begin flagging the artifacts. Socket and Snyk both have public confirmations within 90 minutes of the initial publish.
- March 31, 01:50: Elastic Security Labs files an advisory naming the RAT and the typosquat-via-dependency mechanism.
- March 31, 03:25: npm staff pull both malicious versions and restore the prior
latestandlegacyreleases. The active window closes. - March 31, throughout the day: post-incident analyses appear from Datadog, Huntress, GitLab, and others. Affected developers who installed during the window are advised to rotate everything that might have been on the machine at install time.
The trojanized dependency was the clever part. The malicious axios versions did not contain the RAT payload directly. They pulled it in via plain-crypto-js, which is a typosquat name that does not exist in legitimate axios’s dependency tree. Tools that diff a release against the previous version would notice “a new dependency appeared”, but most automated scanners are tuned to look for malicious code in the published tarball, not for suspicious additions to dependencies. The trick gives the malicious payload more room (the typosquat can be larger and more complex than something you would inline) and shifts the malicious code to a different package, which complicates remediation.
The RAT
Per the Elastic write-up, the embedded RAT is cross-platform: it has working payloads for macOS (x86_64 and arm64), Windows (x64), and Linux (x86_64 and arm64). It establishes persistence via a launch agent on macOS, a registry run key on Windows, and a systemd unit on Linux. It opens a callback to the attacker’s C2 server and supports remote shell, file exfiltration, and screenshot capture. The Datadog analysis catalogs the network signatures.
That degree of cross-platform polish is not what you write between dinner and bed. Whoever shipped this had the RAT ready and was waiting for a usable axios compromise.
What was supposed to stop this
The maintainer account that published the malicious versions had npm 2FA enabled. That is the standard hardening advice and it did not help, because the attack used an automation token, not an interactive login. This is by now a very familiar shape: 2FA gates the login, not the publish.
npm audit and provenance attestation also did not help in the active window:
npm auditconsults a database of known-bad versions. Those versions were not in the database yet; that is the entire point of “freshly published”.- Provenance attestation, when present, certifies which workflow published which artifact. axios maintainers had not set up trusted publishing on this account, so there was no provenance to check. Even if there had been, hijacking the workflow itself is a known unsolved problem.
Static malicious-code scanners (Socket, Snyk, etc.) did flag the artifacts, but only after roughly 90 minutes. During that 90 minutes, hundreds of thousands of fresh axios installs went out, almost all of them to CI runners doing npm install at the top of a build job. CI is the worst place to be installing during an active supply-chain window, because the secrets harvested from a CI runner are usually production-scoped tokens.
Two things that would have changed the outcome
Dist-tag awareness
The attacker published to both latest and legacy. That second tag is the unusual move. It tells me they did their homework: a non-trivial fraction of axios users are pinned to the 0.x line via axios@legacy or via lockfiles that resolved to the legacy tag at some point. Bumping only latest would have skipped them. Bumping both increased the number of reachable installs.
There is not a clean defense for this. You can pin to specific versions in your lockfile, but you still install whatever version your lockfile points at when you run npm ci on a clean checkout. The dist-tag matters at lockfile generation time. If your lockfile was generated during the active window and was committed to git, you have a malicious version pinned forever.
Waiting
The release-age cooldown idea I have been writing about since September and revisited in December is what would have changed the outcome here. A four-day hold means no one running with the cooldown enabled would have installed axios@1.14.1 during the active window. By the time the cooldown lifted, npm had already pulled the version. The user’s package manager would have moved on to the next available version, which was the safe one.
The trade-off is the same as before: you do not get fresh versions for four days. For a transitive dependency like axios, that is usually fine. Production projects pin via lockfiles and only update on a cadence; a four-day delay on getting a new minor release is invisible. For first-class packages where you are actively tracking upstream, you can opt out per-package or temporarily lower the threshold.
I now consider this less of a “consider this” recommendation and more of a “you should already be doing this”. The native settings exist in pnpm, bun, uv, yarn, deno, and current npm. The details differ enough that manual setup is easy to get wrong.
What I would change about the framing
The press coverage on this has leaned on “100 million weekly downloads” as the headline. That number is correct but it is the wrong attention anchor. The shape that matters is:
A single maintainer account with publish rights to a top-50 package was compromised, used to push a malicious version that was reachable for three hours, and the entire incident hinged on the npm registry’s response time being slower than the install rate during that window.
axios is not the problem. axios is one of about a hundred npm packages where the same shape of incident is possible. The attacker had to choose one; they chose axios because it has the highest install rate per hour, which maximizes the throughput of the three-hour window. Next time, it will be a different package, possibly less popular but selected for some other property (a specific industry, a specific CI pattern, a specific upstream).
The right reaction to this is not “I should be more careful when installing axios”. The right reaction is “I should not be installing any freshly-published version of any package without a delay”.
What I shipped
Hand-rolling cooldown config across pnpm, bun, uv, yarn, deno, and npm is easy to get wrong. That became @happyberg/pkg-quarantine: write the setting, then verify that the package manager is actually enforcing it. The dangerous state is not “no cooldown”; it is “configured-looking cooldown that the package manager ignores.”
References
- Datadog Security Labs: Compromised axios npm package delivers cross-platform RAT
- Elastic Security Labs: Inside the Axios supply chain compromise, one RAT to rule them all
- Huntress: Supply Chain Compromise of axios npm Package
- Malwarebytes: Axios supply chain attack chops away at npm trust
- CISA: Widespread npm Supply Chain Compromise (Advisory AD-2026-002)