Back to Blog
Laptop showing npm package security alert and dependency warning overlay
Security
May 25, 2026
9 Min Read

The Three Hours Axios Belonged to Someone Else

I haven’t written in a long time. It wasn’t because of burnout or any big reason, just the usual pattern of meaning to write and then not doing it, again and again, until it starts to feel like a choice. But when this happened, I wanted to say something. So here we are.

Two years ago, almost to the week, the xz backdoor nearly took the internet apart. An engineer upgraded Debian, noticed SSH connections were taking about 400 milliseconds longer than they should, and got annoyed enough to go looking. What he found was a backdoor sitting inside a compression library, patiently waiting to hand remote access to essentially every Linux server on the planet. That’s the whole story of how it got caught. Somebody was bothered by a fraction of a second.

What happened to axios this March is the closest thing since then, and the way it unfolded shows how much has changed in two years, and how much has stayed the same.

Let’s start with what axios is, because its scale matters. It’s a small library for making HTTP requests. Matt Zabriskie released the first version in 2014, and since then it’s become one of those behind-the-scenes tools everyone uses: over a hundred million downloads each week, and about 174,000 other packages depend on it. It’s used in front-ends, back-ends, mobile apps, and build pipelines. If you’ve worked with JavaScript professionally, you’ve probably shipped axios, even if you didn’t choose it yourself.

Infographic showing axios's scale: over 100 million weekly downloads and 174,000 dependent npm packages across web, mobile, and backend code

The lead maintainer was Jason Saayman from South Africa. He had two-factor authentication enabled on almost everything. The project published new releases through GitHub Actions with short-lived credentials, which is exactly the kind of setup security experts recommend. By any reasonable measure, he was doing things right.

None of that mattered, because the attackers didn’t go after the account. They went after him.

For about two weeks, someone pretending to be a startup founder worked to build a relationship. They copied a real company and a real person’s identity. There was a Slack workspace, complete with believable channels, a team with LinkedIn profiles, and even fake accounts for other open source maintainers to make it feel real. Meetings were scheduled and then rescheduled, just like when people are busy. Nothing felt rushed. Saayman later said it didn’t feel urgent, pressing, or suspicious at all. He called it ‘crazy disarming’.

The last step was a Teams call with what looked like several people on it. Mid-call, a prompt appeared saying something on his machine was out of date. He installed the missing piece, figuring it was a Teams thing.

It was a remote access Trojan. From there, they had his laptop, and through his laptop, his credentials.

flowchart LR
    A([Fake founder + cloned<br/>company, fake Slack]) --> B[Trust built over<br/>~2 weeks of meetings]
    B --> C[Teams call with<br/>fake attendees]
    C --> D[Fake system-update<br/>prompt]
    D --> E[RAT installed<br/>on his laptop]
    E --> F([Laptop + npm<br/>credentials compromised])

    style A fill:#fef3c7,stroke:#d97706,color:#78350f
    style F fill:#fee2e2,stroke:#dc2626,color:#7f1d1d

Here’s what makes the 2FA issue less confusing. The secure release process used temporary tokens that expire. But there was also an old, long-lived npm token still around, and that type of token doesn’t require a second factor. It just works. The attacker used it to publish from the command line, changed the account’s email to one they controlled, and that was it.

They’d been getting ready. A day earlier, a package called plain-crypto-js appeared on npm, a clean, harmless copy of a real library, published under a throwaway address purely to give the name some history. Just before midnight UTC on March 30, a second version of it went up. Same name, same innocent appearance, one addition: a script that runs automatically the moment the package is installed.

At 21 minutes past midnight, axios@1.14.1 shipped. Thirty-nine minutes later, axios@0.30.4, the legacy branch, so anyone still on the old line got hit too.

Later, someone compared the good version to the compromised one across all 86 files in the package. Only one file had changed: the manifest, which had a new version number and a single new dependency. Not a single line of axios itself was altered. That new dependency isn’t imported anywhere in the code, doesn’t do anything, and isn’t used. Its only purpose is to make sure that installing axios also installs it, and installing it runs the script.

Diagram comparing clean and compromised axios package.json files, showing the single added dependency that auto-runs a malicious install script

The script was small and hidden under two layers of obfuscation. It checked which operating system it was on and downloaded a matching payload from a server that made its traffic look like normal npm registry activity. Then it cleaned up after itself, deleting its own file, removing the manifest that revealed it, and replacing it with a clean copy. Anyone checking later would see what looked like a normal package. From the start of npm install to the first call home took about a second.

What it left behind was a remote access tool, built three times, once for macOS, once for Windows, and once for Linux, so it would work anywhere. It sent back your hostname, username, running processes, and lists of your home folder, Desktop, and Documents every minute. It could run scripts and install or run other programs. On a developer’s machine, that means access to everything: cloud credentials, SSH keys, tokens, .env files, and browser sessions.

Icon infographic showing what the axios attack's remote access trojan could collect and do, including system data, file listings, and credential exposure

It also had a hard-coded browser user-agent string that pretended to be Internet Explorer 8 on Windows XP. That detail genuinely made me laugh. On a Mac in 2026, that’s not hiding; it’s drawing attention.

The whole thing wasn’t as polished as it might sound. The Linux version crashed right away inside containers and CI runners because it tried to find the logged-in username where there wasn’t one. The Windows version had a bug: it set up persistence but never started its main routine. Only the Mac build worked as planned. Someone released this before it was ready.

Detection was where things felt different from xz. Automated scanners found the problem within minutes of publication. One flagged it at 1:04 am, forty-three minutes after the first upload. A security firm later found that the earliest infection on a monitored machine happened just 89 seconds after the package went live. Developers filed GitHub issues, but the attacker deleted them using the hijacked account. People started calling out on X for someone at npm to respond.

Then there was a very human bottleneck. The compromised account had more permissions on the repository than anyone else. Another collaborator had to open the deprecation request and contact npm staff directly. Around 3:15 am, npm removed both versions, and the default tag returned to the last good release. The whole process took about three hours from start to finish.

Timeline of the axios npm attack from the first malicious package upload to npm removing it roughly three hours later

Cleanup was straightforward. Saayman wiped every device he used and reset all his credentials, including personal ones. The advice to everyone else was just as direct: if you installed during the affected window, treat your machine as fully compromised, change every secret on it, and rebuild. There’s no CVE for this, because axios itself wasn’t broken. The code was fine. What was lost was trust.

(Two weeks later, a genuine axios bug did turn up and got a CVE and a fix in 1.15.0. Different story entirely, but the headlines blurred together, and people are still conflating them.)

Google’s threat intelligence team traced the attack to a North Korean group they’ve tracked since 2018, motivated by money instead of politics. Microsoft came to the same conclusion on its own. The same fake-founder persona had also targeted other maintainers, including a Node.js core contributor and the person behind mocha. This wasn’t just one target. It was a map of who controls the ecosystem.

The confirmed damage was less than the download numbers might suggest. At least 135 machines were seen calling home during the attack window, but that’s just a minimum, one company’s data. Another firm found 19 of its customers affected, mostly in Europe, since 1 am UTC is the middle of the European workday and CI pipelines run on their own schedules. No stolen cryptocurrency has been linked to this. Instead, what’s out there are credentials, an unknown number, quietly useful for whatever comes next.

The main effect is a shift in where the industry sees the weak spot. Most security improvements in recent years focused on the publishing step, making sure it’s really you and the build is clean. This attack got around that by taking over the maintainer’s laptop, so all the checks still passed. npm’s response, released in May, is that now a person must pass a live 2FA check before a package can be installed, no matter how it was built. Package managers also let you block anything published in the last few days, which would have stopped this attack, since the malicious dependency was only hours old.

What stands out to me is the contrast. The xz issue was caught because one person noticed a 400-millisecond delay. This time, machines and several people caught the problem within minutes, just as we hoped the system would work. Yet 135 laptops were still compromised because the gap between publishing and detection was three hours, and in this context, three hours is a long time.

Comparison chart of the xz and axios supply chain attacks, showing how each was caught and the outcome for each

Filed Under

Join the Conversation

This dispatch is part of an ongoing series on the future of intelligence. Share your perspective or subscribe for more.

Weekly dispatches. No spam. Ever.