How attackers turned a maintainer's stolen npm token into a backdoor in 100 million weekly downloads of Axios
On the night of March 30, 2026, attackers compromised the npm account of one of Axios's lead maintainers and used it to publish two poisoned versions of the JavaScript HTTP client to the public registry. The malicious versions didn't change a single line of Axios source code. They added one new dependency, and that dependency installed a program that gave attackers full remote control of every developer machine and CI runner that ran npm install during a three-hour window. By the time npm pulled the bad versions, hundreds of organizations had already been compromised, including ones that had never directly installed Axios.
This wasn't a vulnerability in Axios. It was a failure of the trust model that lets a single person publish code to millions of machines.
Narrative Β· 6 min read
The Context
Axios is one of the most widely used pieces of code in modern software. It's a library that helps JavaScript programs talk to other programs over the internet, fetching data from APIs, submitting forms, syncing state between systems. It powers a meaningful fraction of the apps and services people use every day, often invisibly. Roughly 100 million developers and automated systems install it every single week.
The library is maintained by a small group of unpaid volunteers. Like thousands of other foundational open-source projects, the entire pipeline that distributes Axios to the world depends on a handful of people keeping their personal accounts secure. The lead maintainer's npm account holds the keys to everything. Whoever controls that account can publish a new version of Axios that automatically reaches millions of installations within minutes, no review, no second pair of eyes. That trust model is the entire system.
The attack didn't exploit a flaw in Axios's code. It exploited the trust model itself, and that's worth understanding in detail.
The Attack, Phase by Phase
Phase 1: Pre-Staging
Eighteen hours before the attack, a brand-new npm account published a small, harmless package called plain-crypto-js. The package did nothing malicious. It contained ordinary cryptography helper functions, the kind that would pass any quick review. By publishing it early, the attackers gave the package a brief history on the registry, a few hours of existing as a legitimate-looking entry rather than something that appeared moments before being used.
The malicious version of plain-crypto-js would be published later. For now, the package was a placeholder, sitting in the npm registry, waiting.
Phase 2: Account Compromise
The next step was the hardest part of the operation. The attackers needed to take over the npm account of jasonsaayman, the lead Axios maintainer. Exactly how this happened is still unknown. The maintainer had two-factor authentication enabled on his account, which should have prevented a basic password attack. He later wrote in a public GitHub thread that he was "trying to get support to understand how this even happened."
What investigators have pieced together is that the attackers didn't need to defeat two-factor authentication directly. The Axios project had a long-lived publishing token, a credential that lets a script publish new package versions without going through the normal login flow. That token was meant to power the project's automated release pipeline. Whoever obtained it could publish to npm as the maintainer with no password and no second factor required. Once the attackers had it, the account was effectively theirs.
The first thing they did was change the email address on the account, locking the real maintainer out of his own recovery flow.
Phase 3: Publishing the Payload
With the npm account under their control, the attackers had a publishing window. They moved fast.
First, they published an updated version of plain-crypto-js. The package that had been sitting innocently on the registry for 18 hours suddenly had a new release, and that release contained a hidden script designed to download and run malware. Because automated systems trust packages they've already seen, this update drew far less attention than a brand-new malicious package would have.
In quick succession, the attackers published a new version of Axios itself: 1.14.1, marked as the latest release. The Axios source code in this version was unchanged. The only difference was a single new line in the project's manifest file, declaring that Axios now depended on plain-crypto-js.
Shortly after, the attackers published a second poisoned version of Axios: 0.30.4, marked as the legacy release. This was a calculated move. Many older projects pin themselves to Axios 0.x and never receive 1.x updates. By poisoning both branches, the attackers reached organizations on both modern and legacy Axios at once.
The whole operation took less than an hour.
Phase 4: Execution
When a developer or a server runs npm install, npm reads the project's manifest file, downloads every required package, and unpacks them into a folder on the machine. Routine background work that happens millions of times an hour around the world.
For anyone whose system ran npm install during the three-hour window the poisoned versions were live, that routine became a compromise. Here's the sequence:
- npm sees that the project depends on Axios, and downloads it.
- npm sees that Axios depends on
plain-crypto-js, and downloads it. - npm runs the postinstall hook hidden inside
plain-crypto-js. - The hook detects the operating system, then downloads malware tailored to it: one version for Mac, one for Windows, one for Linux.
- The malware installs itself, opens a connection back to the attackers, and waits for instructions.
- The hook deletes itself and rewrites the manifest to remove all evidence.
From that moment, the attackers had remote control of the machine. A developer inspecting the contents of plain-crypto-js afterward would find a clean, ordinary-looking package, with no indication that malware had ever been installed.
Within 89 seconds of the first poisoned Axios version going live, the first compromised machine appeared in security telemetry. Within three hours, before npm pulled the bad versions, hundreds of organizations had executed the malware, including organizations that had never directly installed Axios. They had been hit because something else they used depended on it.
What Made This Possible
Three things had to be true for this attack to work, and all three are true of the open-source software ecosystem at large.
-
A single human being can hold the keys to software running on millions of machines. Open-source projects are usually maintained by volunteers, often by one or two people, often as a side hobby alongside a day job. When that person's account is compromised, every downstream user is exposed at once. There is no committee, no review board, no second signature required to push a new release.
-
Package managers run code automatically when packages are installed. The postinstall hook that executed the malware in this attack isn't a flaw or a bug. It's a feature, used legitimately by thousands of packages every day for ordinary setup tasks. The same mechanism that lets a graphics library compile itself for your specific computer also lets a malicious package run anything the attackers want, the moment it lands on your machine.
-
Most software depends on far more code than its authors realize. A typical web application might directly install fifty packages, but those packages depend on hundreds more, which depend on thousands more. Most teams cannot list every package their software is running. The result is that an attack on a single popular library reaches countless downstream projects whose authors have never heard of the compromised package, let alone consented to its presence in their code.
The system assumes that anyone allowed to publish a package is trustworthy. That assumption is now the weakest link in modern software.
None of these three conditions is changing soon.
What Should Have Stopped This
Two defenses could have prevented this attack from reaching most of the organizations it hit. Neither is exotic. Both are widely known.
The first is lockfile enforcement. A lockfile is a record of exactly which versions of every package a project has approved to use. When a build system uses lockfiles strictly (with the npm ci command instead of npm install), it refuses to install anything that isn't already in the lockfile, even if a newer version exists in the registry. Organizations that enforced lockfiles in their CI pipelines were largely unaffected, because the poisoned Axios versions had never been approved into their lockfiles.
The second is removing long-lived publishing tokens. Axios had begun configuring OIDC Trusted Publishing, a more secure system where each publish requires fresh authentication tied to a specific code change. But the project still kept its old long-lived token as a backup. The attackers used the token. The newer system never got the chance to deny them.
Both defenses existed. Both were known. Neither was fully deployed.
The Takeaway
The Axios incident isn't going to be the last attack of its kind, and it isn't even the worst. The mechanism behind it (patient account compromise, a quietly-added dependency, automatic execution on install) has been used before, and will be used again, against any popular package whose maintainer can be reached. The interesting question for any organization that depends on open-source software isn't whether to trust the ecosystem. It's how to make the next compromise less consequential when it happens, because it will. The Axios maintainers did almost everything right, and it still wasn't enough.
Pattern to remember: If attackers can compromise a single maintainer account, they don't need any vulnerabilities.
What changed: Package managers have become an initial access vector, and most application security programs were built before that was true.
Technical Deep Dive Β· 3 min
The Technical Mechanism
The malicious plain-crypto-js@4.2.1 package contained a setup.js file that ran during the postinstall lifecycle hook. The script used two layers of obfuscation: reversed Base64 encoding with substituted padding characters, and an XOR cipher with the key string OrDeR_7077. Once deobfuscated, the script called os.platform() to identify the host operating system, then issued an HTTP POST request to the command-and-control server at sfrclak[.]com:8000 (resolved IP 142.11.206.73) with a path of /6202033. The C2 returned a platform-specific second-stage payload: a Mach-O Universal binary for macOS, a PowerShell remote-access trojan for Windows, or a Python script for Linux.
All three platform variants used the same C2 protocol (HTTP POST with Base64-encoded JSON), the same 60-second beacon interval, and an identical anachronistic User-Agent string spoofing Internet Explorer 8 on Windows XP. The Windows variant established persistence via a registry Run key pointing to a hidden batch file at %PROGRAMDATA%\system.bat. The macOS variant wrote its binary to /Library/Caches/com.apple.act.mond, mimicking Apple's daemon naming convention. The Linux variant did not establish persistence, suggesting the attackers expected Linux targets to be ephemeral build runners rather than long-lived workstations.
After payload delivery, setup.js deleted itself and replaced the package's package.json with a clean stub copied from package.md, removing the postinstall hook from the manifest. The node_modules/plain-crypto-js/ directory subsequently appeared to contain a normal cryptography library, with no observable evidence of compromise.
CVE and Advisories
No formal CVE was issued for this incident, as both poisoned package versions were removed from the npm registry within three hours of publication. Snyk published advisories for the affected packages: SNYK-JS-AXIOS-15850650 for Axios 1.14.1 and 0.30.4, and SNYK-JS-PLAINCRYPTOJS-15850652 for plain-crypto-js@4.2.1. The npm Security team published an advisory through the GitHub Advisory Database covering the same versions.
MITRE ATT&CK Mapping
| Technique ID | ATT&CK name | How it appeared |
|---|---|---|
| T1195.002 | Compromise Software Supply Chain | Attackers gained publish access to the Axios npm package by compromising a maintainer's long-lived publishing token, then released two malicious versions to the public registry. |
| T1059.007 | Command and Scripting Interpreter: JavaScript | The postinstall hook executed a Node.js script that performed OS detection, payload retrieval, and payload execution entirely in JavaScript before the platform-specific RAT was launched. |
| T1071.001 | Application Layer Protocol: Web Protocols | All three platform variants of the RAT used HTTP POST requests with a spoofed Internet Explorer 8 User-Agent to communicate with the command-and-control server at sfrclak[.]com:8000. |
Indicators of Compromise
Indicators of compromise (file hashes, domain names, IP addresses, registry keys, file paths) for this attack are published in detail in the primary sources linked below. CyberBytes Daily does not reproduce IOCs, because IOC lists go stale (domains get sinkholed, hashes get recompiled in fresh attacks) and defenders are best served by the original, continuously-updated sources. The most authoritative IOC publications for this incident are Huntress (full IOC table including all platform variants), Snyk (Snyk Vulnerability Database entries), and Elastic Security Labs (binary analysis with hashes).
Attribution
Microsoft Threat Intelligence attributed this campaign to a North Korean state-aligned actor it tracks as Sapphire Sleet. Google Threat Intelligence Group attributed the activity to UNC1069, a suspected DPRK-linked threat cluster. Elastic Security Labs noted that the macOS payload contains code overlaps with backdoors previously attributed to BlueNoroff (a North Korean actor) and the RustBucket malware family. The internal project name macWebT referenced in the macOS binary aligns with BlueNoroff's documented webT module from prior campaigns. None of these attributions has been confirmed by an authoritative incident response report from the Axios maintainers or npm.
Primary Sources
- 01.Supply Chain Compromise of axios npm Package
Huntress Β· March 31, 2026
- 02.Axios npm Package Compromised: Supply Chain Attack Delivers Cross-Platform RAT
Snyk Β· March 31, 2026
- 03.Axios: One RAT to Rule Them All
Elastic Security Labs Β· April 1, 2026
- 04.axios Compromised on npm: Malicious Versions Drop Remote Access Trojan
StepSecurity Β· March 31, 2026
- 05.axios npm Package Compromised: Initial Public Alert
Socket Security Β· March 31, 2026