• Agents
  • Docs
  • Pricing
  • Blog
Log in
Get started

Security for apps built with AI. Paste a URL, get a report, fix what matters.

Product

  • How it works
  • What we find
  • Pricing
  • Agents
  • MCP Server
  • CLI
  • GitHub Action

Resources

  • Blog
  • Docs
  • FAQ
  • Glossary

Security

  • Supabase Security
  • Next.js Security
  • Lovable Security
  • Cursor Security
  • Bolt Security

Legal

  • Privacy Policy
  • Terms of Service
  • Cookie Policy
  • Imprint
© 2026 Flowpatrol. All rights reserved.
Back to Blog
Case Study

The 39-Minute Window: North Korea Compromised axios and It Landed in Your node_modules

On March 31, 2026, a North Korean state actor poisoned axios — downloaded 100 million times per week — for 39 minutes. Every developer or CI pipeline that ran npm install during that window got a remote access trojan. Here's what happened and what to check right now.

Flowpatrol TeamApr 2, 202610 min read
The 39-Minute Window: North Korea Compromised axios and It Landed in Your node_modules

Thirty-nine minutes

At 14:07 UTC on March 31, 2026, two new versions of axios appeared on npm. They looked clean. The version numbers were plausible. The package metadata was identical to the real thing. The only difference was a phantom dependency nobody had ever heard of — plain-crypto-js — and a postinstall hook that ran the moment you finished installing.

At 14:46 UTC, npm's security team yanked both versions. The window was 39 minutes.

In those 39 minutes, axios — the HTTP library that ships in 80% of cloud environments and pulls 100 million downloads every week — delivered a remote access trojan to every machine, CI runner, and Vercel build that ran npm install. The attacker was UNC1069, also tracked as Sapphire Sleet, a North Korean state actor previously linked to a string of cryptocurrency heists. This was not opportunistic. It was deliberate, precise, and they got in through the front door.


Why axios was the perfect target

If you've built a Node.js backend in the last eight years, you've used axios. If an AI tool built one for you, it almost certainly used axios too.

axios is a promise-based HTTP client for Node.js and the browser. It handles API calls, wraps fetch with a cleaner interface, manages interceptors, and auto-serializes JSON. It is not exotic or optional — it's boilerplate. It shows up in the package.json of Express APIs, Next.js backends, serverless functions, and CLI tools. Google Cloud Threat Intelligence estimated it is present in roughly 80% of scanned cloud environments.

That ubiquity made it valuable. A compromised axios doesn't land in one project. It lands in the dependency tree of millions of projects simultaneously. The attacker didn't need to find a zero-day in Node.js or compromise a cloud provider. They just needed 39 minutes.

The account they compromised belonged to axios's lead maintainer. The method was credential theft — stolen credentials, not a brute force attack or a phishing lure against a junior contributor. Google Cloud Threat Intelligence attributed the compromise to UNC1069 based on tooling, infrastructure, and behavioral patterns consistent with prior Sapphire Sleet operations. Microsoft Security Blog corroborated the attribution independently.


The phantom dependency trick

The payload wasn't in axios itself. That's the clever part.

The backdoored axios versions added a single new dependency: plain-crypto-js. The name was chosen to sound like a legitimate cryptography utility — the kind of thing an HTTP library might plausibly reach for. The package had been pre-staged on npm weeks earlier, sitting dormant, waiting.

Inside plain-crypto-js was a postinstall hook. This is a standard npm mechanism: when a package is installed, npm automatically runs any script listed under "postinstall" in the package's package.json. It's meant for things like compiling native bindings or generating configuration files.

{
  "scripts": {
    "postinstall": "node setup.js"
  }
}

In plain-crypto-js, that script was WAVESHAPER.V2 — a cross-platform Remote Access Trojan documented by Elastic Security Labs. It ran at install time, not at runtime. You didn't need to call any axios function. You didn't need to start your server. The moment npm install completed, the RAT was already running.

WAVESHAPER.V2 establishes a persistent remote connection, allowing the attacker to execute commands, exfiltrate files, and pivot through the compromised environment. Elastic Security Labs described it as a full-featured RAT with cross-platform support — Linux, macOS, and Windows. CI pipelines. Docker build containers. Developer laptops. All of them.

Diagram showing the 39-minute attack chain from account compromise through RAT installation

postinstall scripts are npm's original sin. They run with the same privileges as the user invoking npm install. In a CI environment, that's often a service account with broad access to secrets, environment variables, and cloud credentials. There's no sandbox. No prompt. No warning. The script runs because the package asked it to, and that's enough.


Who is UNC1069 / Sapphire Sleet

UNC1069 is a North Korean threat actor tracked under multiple names across different intelligence vendors: Sapphire Sleet (Microsoft), CL-STA-0237 (Palo Alto), and overlapping with clusters previously associated with the Lazarus Group umbrella.

The actor is primarily financially motivated. Prior operations attributed to UNC1069 include targeted social engineering campaigns against cryptocurrency firms, fake job interview lures that deliver malware to developers, and supply chain staging attacks — pre-publishing packages and waiting for the right moment to weaponize them.

The axios operation fits the pattern. The plain-crypto-js package was registered and published weeks before the attack, giving it a history on npm that made it appear less suspicious than a brand-new package. The malware payload (WAVESHAPER.V2) is consistent with tooling previously attributed to the actor by Elastic Security Labs. The targeting was precise: axios's maintainer account, not a random small package maintainer.

Google Cloud Threat Intelligence published the initial attribution. Microsoft Security Blog confirmed independently. Elastic Security Labs documented the payload. Wiz's cloud scanning data provided the exposure estimate: 3% of scanned environments showed indicators of compromise in the hours following the incident — a number that, against the scale of axios's install base, represents an enormous number of affected machines.


Why your vibe-coded app was specifically in the crosshairs

Here's where this hits different for builders who shipped with Lovable, Bolt, Cursor, v0, or Claude.

When an AI tool generates a backend for you, it writes a package.json. That file almost always includes axios, because axios is in the training data for millions of Node.js projects. And it looks like this:

{
  "dependencies": {
    "axios": "^1.7.9",
    "express": "^4.18.2",
    "dotenv": "^16.0.3"
  }
}

See that ^? That's a caret range. It means "install version 1.7.9 or any compatible newer version." When you run npm install, npm fetches whatever the current latest compatible release is — including a version published 15 minutes ago by an attacker who just compromised the maintainer's account.

Pinned versions look different:

{
  "dependencies": {
    "axios": "1.7.9",
    "express": "4.18.2",
    "dotenv": "16.0.3"
  }
}

No caret. Exact version. npm installs what you specify. A backdoored 1.7.10 never touches your project.

Caret ranges are the default because they're convenient. You get bug fixes and patches automatically. The tradeoff — automatic uptake of any newly published version within the range — is something most developers accept without thinking about it. AI tools follow the same convention because it's what the entire ecosystem does.

But every time you rebuild your Docker image, every time your CI pipeline runs npm install fresh, every time you deploy to Vercel or Render — you're pulling whatever npm says is the current latest. During a 39-minute window on March 31st, that was a RAT.


What to check right now

If you have any Node.js project that was built or deployed in the last 48 hours, run through this table before you do anything else.

CheckCommand / ActionWhy it matters
Look for plain-crypto-js in your installfind . -path "*/node_modules/plain-crypto-js" -maxdepth 5The phantom dependency — its presence confirms exposure
Check npm install logs for March 31Review CI logs, Vercel build logs, or local shell historyIdentify whether you installed during the 39-minute window (14:07–14:46 UTC)
Confirm your axios version is cleannpm list axios — safe versions are ≤1.7.9 or ≥1.14.1The backdoored versions are 1.8.2 and 1.8.3
Delete and reinstall node_modulesrm -rf node_modules && npm ci

The safe versions of axios to use right now are anything at or below 1.7.9, or the patched release 1.14.1 and above. The two backdoored versions — 1.8.2 and 1.8.3 — have been removed from npm, but if your lockfile referenced them and you haven't reinstalled, they may still be in your node_modules.


The bigger lesson: maintainer accounts are the new attack surface

This is not the first time a state actor has gone after a package maintainer account. It will not be the last.

The npm registry's trust model is fundamentally personal. A package is trusted because its maintainer is trusted. The maintainer is trusted because they published the original package and have maintained it for years. That trust is built over time and is very real — and it lives in a set of credentials on a laptop somewhere.

When an attacker steals those credentials, they inherit all of that accumulated trust instantly. npm sees a valid token. The registry accepts the publish. The CDN caches the new version. Your npm install fetches it. Nobody in that chain asked whether the human behind the token had been compromised.

The ecosystem has responded to previous incidents with new tooling. npm now supports granular publish tokens, two-factor requirements for popular packages, and provenance attestation via Sigstore. These are meaningful improvements. But adoption is uneven, especially for older packages with established maintainers who set up their accounts years ago.

The axios attack worked because the attacker only needed to defeat one person's credential hygiene to get access to 100 million weekly downloads. That's an asymmetric trade. Until package signing and provenance verification are universal and verified at install time, the gap exists.


Five things to do before your next deploy

  1. Check for plain-crypto-js right now. Run find . -path "*/node_modules/plain-crypto-js" -maxdepth 5 in every project directory. If it's there, treat the machine as compromised and rotate all credentials stored on it or accessible from it.

  2. Pin axios to a safe version. Open your package.json, find axios, and change ^1.x.x to 1.7.9 (the last clean version before the attack) or 1.14.1 (the patched release). Remove the caret. Commit the change and the updated lockfile together.

  3. Switch npm install to npm ci in CI. npm ci installs exactly what's in your lockfile and fails loudly if there's a mismatch. It's one line change in your GitHub Actions or build config. Make it today.


Attribution and technical details for the March 31, 2026 axios supply chain attack were sourced from Google Cloud Threat Intelligence, Microsoft Security Blog, Elastic Security Labs, Wiz, and SecurityWeek. The WAVESHAPER.V2 payload analysis is documented by Elastic Security Labs. Attribution to UNC1069 / Sapphire Sleet is based on independent analysis by Google Cloud Threat Intelligence and Microsoft.

Back to all posts

More in Case Study

The Base44 Auth Bypass: Wix Paid $80M, Then Researchers Bypassed Every Login With Two API Calls
Apr 2, 2026

The Base44 Auth Bypass: Wix Paid $80M, Then Researchers Bypassed Every Login With Two API Calls

Read more
A 4-Digit PIN Was Guarding 3.2 Million Health Records
Apr 2, 2026

A 4-Digit PIN Was Guarding 3.2 Million Health Records

Read more
Polyfill.io: 380,000 Sites, One CDN, One Domain Sale
Apr 2, 2026

Polyfill.io: 380,000 Sites, One CDN, One Domain Sale

Read more
Removes any installed payload; npm ci uses lockfile exactly
Run npm auditnpm auditFlags known compromised versions once advisories are published
Rotate cloud credentials on affected machinesAWS, GCP, and Azure credential rotation for any machine that installed during the windowWAVESHAPER.V2 exfiltrates environment variables and credential files
Check for unexpected processesps aux on any machine that installed during the windowA running RAT may still be active

Rotate credentials on any machine that built during the window. If a machine ran npm install between 14:07 and 14:46 UTC on March 31, 2026 — a CI runner, a dev laptop, a build container — assume its environment variables and credential files were exfiltrated. Rotate everything: AWS keys, GCP service accounts, API tokens, .env secrets.

  • Scan your deployed app with Flowpatrol. WAVESHAPER.V2 modifies behavior at the network level — unexpected outbound connections, unusual process activity. A scan of your live app can surface anomalous patterns that suggest an active compromise. Paste your URL, get a report, know where you stand before your next release.