- My project: Hermes IDE | GitHub
- Me: gabrielanhaia
One hundred million weekly downloads. One stolen token. One backdoor.
Axios 1.14.1 dropped on March 31, 2026. So did 0.30.4. Both looked like routine patch releases. Neither came from the axios team.
They came from North Korea.
For roughly three hours, every npm install that resolved to either version pulled in a dependency called plain-crypto-js. That dependency deployed WAVESHAPER.V2, a multi-platform backdoor built to survive deletion, phone home to a command-and-control server, and hand a remote shell to the attacker. Windows, macOS, Linux. All covered. All weaponized.
Google's Threat Intelligence Group attributed the operation to UNC1069, a North Korea-nexus cluster active since 2018. Not a teenager with a typosquat. A government-funded team with a track record measured in billions of stolen dollars.
Three hours sounds short. At axios's scale, it wasn't.
How a Single Token Burned the Supply Chain
The attack surface was embarrassingly small. One npm authentication token, belonging to one axios maintainer, in the hands of one threat actor. That was enough.
No MFA bypass was reported. No elaborate zero-day exploit chain. The working assumption across every post-incident analysis is that the token itself was the target. Stolen credentials, phished session, compromised development machine — the exact vector hasn't been publicly confirmed. The outcome, though, is crystal clear.
Here's the hour-by-hour damage.
March 31, early hours (UTC): The attacker authenticates to the npm registry using the compromised token. Two new versions publish almost simultaneously. Version 1.14.1 for the current 1.x release line. Version 0.30.4 for the legacy 0.x line. Both add a single new dependency: plain-crypto-js.
The name was chosen with care. It reads like a boring crypto utility. The kind of package that shows up in a diff and gets waved through because nobody wants to investigate a transitive dependency at 2 AM.
Minutes later: CI/CD pipelines worldwide start resolving the new versions. Any project using caret ranges like ^1.14.0 or lacking a committed lockfile gets the poisoned release automatically. No user action required. No prompt. No confirmation dialog.
~2.5 hours after publication: Security researchers flag the anomaly. The compromised versions get yanked from npm. The maintainer's account is locked and recovered. Advisories begin circulating.
Following days: Microsoft, Google, and Tenable publish formal analyses. Google ties the operation to UNC1069 with high confidence.
The window closed fast. What happened inside it didn't.
Anatomy of the Payload: What plain-crypto-js Actually Executed
Calling this malware "sophisticated" undersells the engineering. It wasn't a single script grabbing environment variables and posting them to a webhook. It was a staged deployment pipeline — built by attackers who clearly understood how developers work.
Stage 1: Fingerprinting the target
The postinstall script fires the moment npm finishes writing the package to disk. It reads process.platform and process.arch, then selects the appropriate payload. Three operating systems. Two CPU architectures for macOS (x64 and arm64). Someone compiled, tested, and packaged binaries for every platform a working developer might use.
// Simplified reconstruction of the platform check
const os = process.platform; // 'win32', 'darwin', 'linux'
const arch = process.arch; // 'x64', 'arm64'
// Selects and fetches the correct binary for the host
Stage 2: Binary drop
Based on fingerprinting results, the script downloads a platform-native binary from an external server. It writes the binary to a hidden path inside node_modules and executes it. Quick. Quiet. Buried under thousands of other files that nobody ever manually inspects.
Stage 3: WAVESHAPER.V2 takes the machine
The binary is the real weapon. Google's analysts designated it WAVESHAPER.V2, and its capabilities read like a pentester's wish list:
Persistence. It copies itself outside the project directory before doing anything else. Deleting node_modules doesn't touch it. Running npm install with a clean version doesn't remove it. The backdoor relocates to OS-specific persistence locations — LaunchAgents on macOS, systemd user services on Linux, startup folders and scheduled tasks on Windows.
Command and control. It opens a reverse connection to an attacker-controlled C2 server and maintains the channel. Remote shell access. Full.
Exfiltration. Environment variables, SSH keys, cloud credential files, browser session tokens, cryptocurrency wallet data. Everything accessible to the user account that ran npm install.
Survival. Even a factory reset of the project directory leaves the backdoor intact. It's living in the home directory. On the system. Not in the dependency tree.
Removing
node_modulesafter installing a compromised version does nothing. The host machine requires a full incident response investigation.
What UNC1069 was harvesting
North Korean cyber operations aren't about espionage in the traditional sense. They're about money.
The UN estimated DPRK-linked groups stole over $3 billion in cryptocurrency between 2017 and 2023. The pace has accelerated since. UNC1069's target list follows the pattern: crypto wallet keys, AWS/GCP/Azure tokens sitting in ~/.aws/credentials or environment variables, SSH private keys for lateral movement into other systems, and CI/CD secrets that open doors to entire organizations.
A developer laptop with cloud access and deployment permissions is worth more to this group than most corporate database servers.
The Bigger Nightmare: This Wasn't a Solo Operation
The axios compromise would be alarming enough on its own. It wasn't on its own.
Between March 19 and March 27 — days before the axios attack — a separate North Korea-linked group tracked as TeamPCP (UNC6780) executed a parallel campaign through a completely different vector. They didn't steal npm tokens. They exploited GitHub Actions workflows and PyPI packages.
Their target list is almost funny in a gallows-humor kind of way:
Trivy, Aqua Security's vulnerability scanner. The tool organizations use to detect compromised dependencies was itself compromised. KICS (Keeping Infrastructure as Code Secure) got the same treatment. A security tool, backdoored. LiteLLM, a popular LLM proxy library, was hit. So was Telnyx, a cloud communications platform.
TeamPCP deployed SANDCLOCK, a credential stealer purpose-built for CI/CD environments. It harvested cloud tokens, API keys, and pipeline secrets from build runners.
Two groups. Two attack vectors. Multiple high-value targets. All within twelve days.
Whether UNC1069 and UNC6780 coordinated directly or simply operated under the same strategic directive from Pyongyang is unknown. The result is identical either way: a sustained, multi-pronged assault on the open-source supply chain.
Are You Affected? Find Out Right Now.
Stop skimming. Run these commands. The rest of the article isn't going anywhere.
Check the lockfile for the malicious dependency
# npm projects
grep "plain-crypto-js" package-lock.json
# yarn projects
grep "plain-crypto-js" yarn.lock
# pnpm projects
grep "plain-crypto-js" pnpm-lock.yaml
Any match means the compromised version was installed.
Verify your current axios version
npm ls axios
The output shows exactly which version sits in the dependency tree right now. If it reads 1.14.1 or 0.30.4, the machine that ran the install needs investigation. Not just the project. The machine.
Check the npm cache
Even if the current version is clean, the cache remembers.
npm cache ls 2>/dev/null | grep "axios" | grep -E "1\.14\.1|0\.30\.4"
Run npm audit
npm audit
The advisory for the compromised versions should surface here if the dependency tree ever referenced them.
Hunt for WAVESHAPER.V2 persistence artifacts
macOS: Linux: Windows (PowerShell): Any unfamiliar files created on or after March 31, 2026 in these locations warrant immediate escalation to a security team.If you confirmed a compromised version was installed, check these locations
ls -la ~/Library/LaunchAgents/
find ~/.local -type f -newer /tmp 2>/dev/null
find /tmp -name ".*" -type f 2>/dev/null
ls -la ~/.config/systemd/user/ 2>/dev/null
find ~/.local -type f -newer /tmp 2>/dev/null
find /tmp -name ".*" -type f 2>/dev/null
Get-ScheduledTask | Where-Object {$_.Date -gt "2026-03-30"}
dir "$env:APPDATA\Microsoft\Windows\Start Menu\Programs\Startup"
Don't forget CI/CD
This is the check most teams will skip. Don't.
If a CI pipeline ran npm install (not npm ci) during the March 31 window without a pinned lockfile, the build runner may have executed the backdoor. Ephemeral runners like GitHub Actions hosted agents are lower risk because they're destroyed after each job. Self-hosted runners, Jenkins boxes, and long-lived build servers are a completely different situation.
Review outbound network logs from those machines for the March 31 timeframe. Any unexpected connections to unfamiliar IPs during or after the window should be treated as indicators of compromise.
What Every Team Should Do This Week
Today
Run every detection command above. Every project. Every CI environment. Every build server.
Switch CI pipelines from npm install to npm ci. This single change would have blocked the axios compromise entirely for any project with a committed lockfile. npm ci installs exactly what the lockfile specifies and fails hard on mismatches.
# Replace this in every CI config:
npm install
# With this:
npm ci
Pin dependency versions. Caret ranges (^1.14.0) and tilde ranges (~1.14.0) exist for convenience. They also exist as an attack surface. Exact versions in package.json plus a committed lockfile equals a dramatically smaller blast radius.
This week
Audit and rotate all npm tokens.
npm token list
npm token revoke <token-id>
Revoke anything unrecognized. Rotate everything else. If anyone on the team maintains a public package, enforce 2FA on their npm account today, not tomorrow.
Review GitHub Actions workflows for the patterns TeamPCP exploited. Workflows using pull_request_target with a checkout of the PR branch allow external contributors to run arbitrary code with write permissions. That's the exact mechanism UNC6780 used.
Evaluate a registry proxy. Tools like Verdaccio, Artifactory, or npm Enterprise can allowlist specific package versions before they reach developer machines. It's an extra layer that costs time to configure and saves everything when it matters.
Ongoing
Run npm audit as a blocking CI step. Known vulnerabilities should fail the build. No exceptions. No --force to skip warnings.
Subscribe to security feeds. GitHub's advisory database, npm security advisories, and Google Threat Intelligence all published within 48 hours of the axios incident. Teams that monitor these feeds had time to react. Teams that don't monitor them found out from Twitter, days later.
Why npm Keeps Getting Hit
The npm registry is built on a trust model designed for a different era. Anyone with an account can publish. Anyone with a maintainer token can push a new version of an existing package. There's no mandatory code review gate. No build reproducibility requirement. No quarantine period between publication and global availability.
Compare that to Linux distribution package management, where maintainer committees review submissions and reproducible builds can be independently verified. Or mobile app stores, where multi-day review processes and cryptographic signing are table stakes.
npm has a username, a password, and an optional TOTP code. That's the entire barrier between an attacker and one hundred million weekly installs.
Improvements exist. Provenance attestation. Granular access tokens. 2FA support. All voluntary. All opt-in. Adoption rates are growing but remain far from universal.
The gap between npm's trust architecture and the actual threat environment is staggering. Nation-state actors have done the math. Compromising a single developer account on a package registry provides simultaneous access to thousands of downstream organizations. The return on investment is extraordinary, and North Korea has stronger financial incentive than almost any other actor to keep exploiting it.
The Bottom Line
A state-sponsored group compromised one of the most widely installed packages on the planet. The attack was caught in under three hours. That's genuinely fast, compared to supply chain incidents that take weeks or months to surface.
Fast wasn't fast enough.
Every developer who ran npm install without a pinned lockfile during that window was exposed. Every CI pipeline pulling fresh dependencies without npm ci was exposed. Every self-hosted build server that cached the compromised version might still be running a backdoor right now, five days later, with nobody aware.
Treating dependency management as a convenience function rather than a security function is the root cause. Pin versions. Commit lockfiles. Use npm ci in automation. Audit regularly. Monitor advisories. Investigate machines that pulled affected versions, not just the projects.
The axios backdoor lived for under three hours. The next one might not get caught that quickly.
Top comments (0)