DEV Community

Cover image for axios Got Hijacked Today: A Technical Breakdown of the Most Sophisticated npm Supply Chain Attack Yet
CodePawl for CodePawl

Posted on

axios Got Hijacked Today: A Technical Breakdown of the Most Sophisticated npm Supply Chain Attack Yet

If you use axios — and statistically, you do — you need to read this.

On March 31, 2026, two malicious versions of axios were published to npm: 1.14.1 and 0.30.4. The attacker hijacked a lead maintainer's npm account, injected a hidden dependency that deploys a cross-platform RAT, and designed the entire payload to self-destruct after execution. The malicious versions were live for roughly 3 hours before npm pulled them.

This isn't a typosquat. This isn't a random package nobody uses. This is axios — 100M+ weekly downloads, present in virtually every Node.js project that touches HTTP.


What happened

The attacker compromised the npm account of jasonsaayman, the primary axios maintainer. They changed the account email to an anonymous ProtonMail address (ifstap@proton.me) and published the poisoned packages manually via npm CLI, completely bypassing the project's GitHub Actions CI/CD pipeline.

The key forensic signal: every legitimate axios 1.x release is published via GitHub Actions with npm's OIDC Trusted Publisher mechanism — cryptographically tied to a verified workflow. axios@1.14.1 has no OIDC binding, no gitHead, no corresponding GitHub commit or tag. It exists only on npm.

The attacker likely obtained a long-lived classic npm access token. The OIDC tokens used by legitimate releases are ephemeral and scoped — they can't be stolen in the traditional sense.

The attack chain

The attack was pre-staged 18 hours in advance. Here's the timeline:

  • Mar 30, 05:57 UTCplain-crypto-js@4.2.0 published (clean decoy, establishes npm history)
  • Mar 30, 23:59 UTCplain-crypto-js@4.2.1 published (malicious payload added)
  • Mar 31, 00:21 UTCaxios@1.14.1 published via hijacked account
  • Mar 31, 01:00 UTCaxios@0.30.4 published (legacy branch, 39 min later)
  • Mar 31, ~03:15 UTC — npm pulls both malicious axios versions

Both malicious axios versions add exactly one new dependency: plain-crypto-js@^4.2.1. This package is never imported or required anywhere in the axios source. Its sole purpose is to execute a postinstall hook.

// axios@1.14.0 deps: follow-redirects, form-data, proxy-from-env
// axios@1.14.1 deps: follow-redirects, form-data, proxy-from-env, plain-crypto-js ← new
Enter fullscreen mode Exit fullscreen mode

A dependency that exists in package.json but has zero usage in the codebase is a high-confidence indicator of a compromised release.

Inside the dropper

The setup.js file (4209 bytes, minified) uses a two-layer obfuscation scheme:

  1. XOR cipher with key derived from "OrDeR_7077" — only the digits 7,0,7,7 survive JavaScript's Number() parsing, rest becomes NaN → 0 in bitwise ops
  2. Reverse + base64 decode as an outer layer

Once decoded, it dynamically loads child_process, os, and fs at runtime to evade static analysis, then branches by platform:

macOS: Writes an AppleScript that downloads a RAT binary to /Library/Caches/com.apple.act.mond — a path mimicking an Apple system daemon. Executed via osascript, then the script self-deletes.

Windows: Copies PowerShell to %PROGRAMDATA%\wt.exe (disguised as Windows Terminal), writes a VBScript that fetches and runs a hidden PowerShell RAT with -ExecutionPolicy Bypass -WindowStyle Hidden. Both temp files self-delete.

Linux: Direct curl to download a Python RAT to /tmp/ld.py, executed via nohup to detach from the process tree.

All three payloads phone home to sfrclak.com:8000 with platform-specific POST bodies (packages.npm.org/product0|1|2) — deliberately crafted to look like npm registry traffic in network logs.

The self-destruct sequence

After launching the payload, setup.js performs three cleanup steps:

  1. Deletes itself (fs.unlink(__filename))
  2. Deletes package.json (which contains the postinstall hook)
  3. Renames a pre-staged package.md to package.json — a clean manifest with no scripts

Post-infection, node_modules/plain-crypto-js/ looks completely clean. npm audit won't flag it. Manual inspection won't catch it. But the existence of the directory itself is proof the dropper ran — plain-crypto-js is not a dependency of any legitimate axios version.

How to check if you're affected

# Check lockfile for compromised versions
grep -E "1\.14\.1|0\.30\.4" package-lock.json

# Check for the malicious dependency
ls node_modules/plain-crypto-js 2>/dev/null && echo "AFFECTED"

# Check for RAT artifacts
ls -la /Library/Caches/com.apple.act.mond 2>/dev/null  # macOS
ls -la /tmp/ld.py 2>/dev/null                           # Linux
dir "%PROGRAMDATA%\wt.exe" 2>nul                        # Windows
Enter fullscreen mode Exit fullscreen mode

Remediation

  1. Pin to safe versions: axios@1.14.0 (1.x) or axios@0.30.3 (0.x)
  2. Remove the malicious package: rm -rf node_modules/plain-crypto-js
  3. If RAT artifacts found: assume full system compromise, rotate ALL credentials (npm tokens, SSH keys, cloud keys, CI/CD secrets, .env values)
  4. Audit CI/CD pipelines for any runs that installed during the window
  5. Block the C2: sfrclak.com / 142.11.206.73

The bigger picture

This is the same pattern we've seen accelerating throughout 2025-2026: maintainer account hijack → manual npm publish → phantom dependency → postinstall dropper. The Shai-Hulud worm, the Qix compromise, Chalk/Debug — all variations on the same playbook.

The uncomfortable truth: npm's security model has a single-point-of-failure problem. Long-lived tokens still exist. Email changes don't require additional verification. Manual CLI publishing can bypass every CI/CD safeguard a project has built. Trusted Publishing (OIDC) is available but not enforced.

Some practical defenses:

  • npm ci --ignore-scripts in all CI/CD pipelines
  • Set ignore-scripts=true in ~/.npmrc for local dev (opt-in to postinstall only when needed)
  • Use lockfiles religiously and review diffs on dependency changes
  • bun and pnpm don't execute lifecycle scripts by default — worth considering
  • Package cooldown policies — most malicious packages are caught within 24 hours

The window of exposure was ~3 hours. The detection came from Socket and StepSecurity within minutes. But for a package with 100M+ weekly downloads, even 3 hours is a massive blast radius.

Pin your versions. Audit your lockfiles. Don't trust latest.


Sources: StepSecurity, Socket, Aikido, axios GitHub issue #10604


Written by An — founder of Codepawl, building open-source developer tools from HCMC, Vietnam.

codepawl.com · X @lunovian · X @codepawl · Discord

Top comments (0)