TL;DR: North Korean hackers built a fake company, complete with a Slack workspace, LinkedIn activity, and a full team of fake profiles, to trick the lead maintainer of axios into installing malware. One Teams meeting later, they had full control of his machine. They used that access to push malicious versions of a library with 100 million weekly downloads. The attack was live for 3 hours. It's the most sophisticated social engineering of an open source maintainer we've seen, and it exposes gaps in npm's security model that no amount of 2FA can fix.
On March 31, 2026, two versions of axios that had never been through the project's CI pipeline appeared on npm. Versions 1.14.1 and 0.30.4 both carried a new dependency nobody had seen before: plain-crypto-js. 1
Within six minutes, Socket's automated scanner flagged the package. 2 Within three hours, npm pulled both versions. But in those three hours, an unknown number of developers, CI pipelines, and production systems had already installed a cross-platform Remote Access Trojan.
The story of how those versions got published is more interesting than the malware itself.
How do you trick someone who maintains code for 100 million developers?
Jason Saayman, the lead maintainer of axios, shared the playbook in the project's post-mortem. 1 It reads less like a hacking story and more like a con movie.
The attackers reached out masquerading as the founder of a real company. They had cloned the founder's identity and the company itself. Then came the invite to a Slack workspace.
This wasn't a hastily thrown-together channel. The workspace was branded with the company's visual identity. It had channels where "team members" shared the company's LinkedIn posts (likely linking to the real company's account). There were fake profiles for the company's team and for other open source maintainers, giving the whole setup social proof.
After establishing trust through the Slack workspace, they scheduled an MS Teams meeting. Multiple people appeared to be on the call. During the meeting, something on Saayman's system was flagged as "out of date." He installed the update, thinking it was related to Teams.
That update was the RAT.
"Everything was extremely well co-ordinated, looked legit and was done in a professional manner," Saayman wrote. 1
Saayman wasn't the only target
Weeks before the axios compromise, voxpelli, a maintainer of packages like Mocha, described a nearly identical approach. 1 Someone invited him to be on a "podcast." A week of lead-up followed: social media images, preparatory interview questions, other guests in a group chat. Everything felt real.
When it came time to "record," the fake streaming website claimed a connection issue and tried to get him to install a non-notarized macOS app. When he refused, they tried a curl command to download and run something. When that failed too, they went dark and deleted every conversation.
"It's creepy how they target you, no matter if they are real people or possibly AI," voxpelli wrote. 1
What the malware actually did
The technical chain was clean. plain-crypto-js@4.2.1 used a postinstall hook to run setup.js, a 4,209-byte dropper obfuscated with reversed Base64 and XOR cipher (key: OrDeR_7077). 2
It deployed platform-specific payloads:
-
macOS: A C++ binary disguised as an Apple system daemon at
/Library/Caches/com.apple.act.mond, supporting remote code execution and process injection 2 -
Windows: Renamed
powershell.exetowt.exe(disguised as Windows Terminal), launched via VBScript 2 -
Linux: A Python script at
/tmp/ld.pyrunning as a detached process 2
All variants beaconed every 60 seconds to sfrclak[.]com. The dropper then cleaned up after itself: deleted setup.js, deleted package.json, and renamed a clean backup to package.json. The directory looked normal after execution. 2
Google/Mandiant attributes the malware to UNC1069, a North Korea-nexus threat actor active since 2018, based on overlap with the WAVESHAPER backdoor family. 3 Microsoft independently attributes it to Sapphire Sleet, also North Korean. 4
2FA didn't matter. That's the real story.
Saayman had two-factor authentication enabled on his npm account. It didn't help.
Once a RAT has full control of your machine, software-based TOTP is just another application the attacker can interact with. They changed his npm email to a Proton Mail address under their control (ifstap@proton.me) and used a long-lived classic npm access token to publish. 5
Here's what makes this worse: axios already had OIDC-based publishing with provenance attestations since 2023. The last four legitimate v1 releases all went through GitHub Actions with Trusted Publishing. The malicious v1.14.1 had neither provenance nor attestations. Any tool checking for this would have flagged it instantly. 6
But npm has no setting to enforce OIDC-only publishing. There is no way to tell the registry: "reject anything not published through CI." The strictest option npm offers still allows local npm publish with a browser-based 2FA prompt, which a RAT can trivially intercept. 6
As contributor shaanmajid put it: "The only mitigation on Axios's end that could have actually prevented this would have been using hardware FIDO2 keys for maintainer npm auth, which can't be hijacked by a RAT." 6
What would have actually prevented this?
Three things, none of which axios alone could control:
1. Registry-level OIDC enforcement. If npm allowed packages to opt in to "reject all non-OIDC publishes," the RAT would have been useless for publishing. Other registries like crates.io already support this. 6
2. Dependency cooldown periods. The malicious versions were live for 3 hours. A 3-day cooldown on new versions (supported by Dependabot, Renovate, uv, and bun via minimumReleaseAge) would have meant zero downloads of the poisoned packages. 6
3. Provenance verification by default. Every legitimate axios v1 release had OIDC provenance. The malicious one didn't. If package managers verified attestations by default instead of opt-in, this would have been caught at install time. 6
The pattern is bigger than axios
This attack follows the playbook Google documented for UNC1069: social engineering that targets individuals in crypto and AI, building elaborate fake identities and companies to establish trust before delivering malware. 7
What's different here is the target. This wasn't a crypto startup founder. It was a maintainer of a general-purpose HTTP library embedded in millions of projects globally. The blast radius isn't one company's treasury. It's the software supply chain itself.
Feross Aboukhadijeh, founder of Socket, summarized it: "This kind of targeted social engineering against individual maintainers is the new normal. It's not a reflection on Jason or the axios team. These campaigns are sophisticated and persistent. We're seeing them across the ecosystem and they're only accelerating." 1
Singapore's Cyber Security Agency issued a formal advisory. 8 Microsoft, Google, SANS, Elastic, Snyk, Datadog, Huntress, and Malwarebytes all published analyses within days.
Key takeaways
- Social engineering is the attack vector. The malware was simple. The social engineering was extraordinary. Fake companies, branded Slack workspaces, multi-person Teams calls, weeks of relationship building.
- Software 2FA is not 2FA when your machine is compromised. Hardware keys (FIDO2/WebAuthn) are the only defense against RAT-based credential theft.
- npm's security model has a structural gap. There is no way to enforce "publish only from CI." Until registries support OIDC-only publishing, every maintainer's laptop is a viable attack surface.
- Provenance attestations work, but nobody checks them. The malicious version was missing attestations that every legitimate version had. The signal was there. The ecosystem isn't wired to use it yet.
-
Dependency cooldowns are free protection. Configure
minimumReleaseAgein your dependency tools. A 3-day delay would have neutralized this entire attack. - Open source maintainers are high-value targets for state actors. This is the new normal.
How to check if you're affected
grep -E "axios@(1\.14\.1|0\.30\.4)|plain-crypto-js" package-lock.json yarn.lock bun.lock pnpm-lock.yaml 2>/dev/null
If you find a match: downgrade to axios@1.14.0 or 0.30.3, remove plain-crypto-js from node_modules, rotate every secret and credential on the affected machine, and check network logs for connections to sfrclak[.]com or 142.11.206.73. 1
I break down stories like this on LinkedIn, X, and Instagram. If this was useful, you'd probably like those too.
Top comments (0)