DEV Community

AskClaw
AskClaw

Posted on

Supply Chain Security: 4 Commands That Would Have Stopped the axios and litellm Attacks

Supply Chain Security

"Classical software engineering would have you believe that dependencies are good (we're building pyramids from bricks), but imo this has to be re-evaluated — I've been increasingly averse to them, preferring to use LLMs to 'yoink' functionality when it's simple enough."
— Andrej Karpathy

He's right. And this week proved it again.


What Happened This Week

Two major supply chain attacks, seven days apart:

axios (npm) — March 31, 2026
Malicious versions 1.14.1 and 0.30.4 published via compromised maintainer credentials. A postinstall script silently dropped a RAT, called home to a C2 server, then self-destructed — leaving no trace in node_modules. Millions of developers had "axios": "^1.13.0" in their package.json. The ^ did the rest.

litellm (PyPI) — March 24, 2026
Version 1.82.8 compromised. Exfiltrated: SSH keys, AWS/GCP/Azure credentials, Kubernetes configs, environment variables, shell history, crypto wallets, SSL private keys, CI/CD secrets, database passwords. 97M downloads/month. Only caught because the malware had an OOM bug that crashed a developer's machine. A cleaner attack would have run undetected for weeks.

The common thread: both attacks lived entirely in install scripts. One flag stops both cold.


The Fix (4 Commands + 1 Config)

Copy these into your project. You're done.

# 1. Never let install scripts run
npm ci --ignore-scripts

# 2. Pin exact versions — strip all ^ and ~ from package.json
#    "axios": "^1.13.0"  →  "axios": "1.13.0"

# 3. Age-check before installing anything new — don't touch < 7 days old
npm view <package> time --json | python3 -c "
import json, sys
from datetime import datetime, timezone
times = json.load(sys.stdin)
latest = max(times.items(), key=lambda x: x[1] if x[0] not in ('created','modified') else '')
published = datetime.fromisoformat(latest[1].replace('Z','+00:00'))
age = datetime.now(timezone.utc) - published
print(f'Latest: {latest[0]}, published {age.days}d ago')
"

# 4. Audit known CVEs on every CI run
npm audit --audit-level=high
Enter fullscreen mode Exit fullscreen mode

Bonus: lock it all in with one .npmrc — commit this to your repo:

# .npmrc — commit this file
ignore-scripts=true       # blocks all postinstall RATs
save-exact=true           # forces exact pins on npm install (no ^ added automatically)
min-release-age=7         # npm v11.10.0+ only — blocks packages < 7 days old
Enter fullscreen mode Exit fullscreen mode

Three lines, committed to the repo. Every developer, every CI run, every npm install — protected by default. No discipline required.

⚠️ min-release-age requires npm v11.10.0+ (Feb 2026). Check with npm --version. On older npm, use the age-check script above manually.

⚠️ Security patch override: when you need to fast-track a verified CVE fix, temporarily override with npm install <pkg>@<version> --ignore-min-release-age (npm v11.10.0+) or remove the line, update, then restore it.


Why Each One Matters

1. --ignore-scripts (or .npmrc)

The entire axios and litellm attack delivery mechanism was a postinstall script. This flag disables preinstall, install, postinstall, preuninstall, and postuninstall for all packages. The payload never executes.

The .npmrc config bakes this into the project permanently — you don't need to remember the flag, and new contributors get the protection automatically.

⚠️ Caveat: packages that compile native addons (e.g. node-gyp, bcrypt) need install scripts. For most web projects: safe.

2. Exact version pins

//  This silently upgraded thousands of projects to axios@1.14.1
"axios": "^1.13.0"

//  This doesn't move without an explicit commit
"axios": "1.13.0"
Enter fullscreen mode Exit fullscreen mode

^ means "any compatible version." In practice it means "pull whatever the maintainer published today." Remove it everywhere.

3. The 7-day rule

Both attacks were caught within hours — but that was luck. Sloppy malware. A competent attacker builds in a delay before the payload activates. The 7-day window filters out rushed attacks. 48 hours catches the sloppiest. 7 days catches most of the rest.

Update type Wait
Feature / minor update 7 days
Verified security patch for known CVE ASAP
Package you've never used before 7 days minimum + manual review

4. npm audit

This catches known CVEs — it won't catch a zero-day attack like axios. But it catches the long tail of everything else. Run it in CI. Block merges on --audit-level=high.


Why Standard Tools Missed This

Tool Why it failed
Trivy, Grype, OSV-Scanner Scan for known CVEs — zero-day has no CVE yet
Renovate / Dependabot Would have automatically pulled in the malicious version
Snyk Same — no CVE, no signal
Socket.dev Closest to catching it — behavioral analysis flags suspicious new publishes

What actually caught the axios attack: runtime network monitoring (StepSecurity Harden-Runner) spotted the C2 callback during a CI run. The malware called home. That's the only reason we know.


The Deeper Fix: Fewer Dependencies

This project (creem-worker) uses zero npm dependencies by design. Pure Node.js. If the runtime provides it, we use it. If it doesn't, we write 20 lines instead of pulling in a library.

Every dependency is a permanent bet that its entire supply chain stays clean — forever. Maintainer accounts get compromised. Packages get abandoned and re-registered. CI pipelines pull from npm at 3am when nobody's watching.

The safest dependency is the one you never added.

When an LLM can write the function in 5 minutes, that's almost always the better call.


Quick Checklist

  • [ ] .npmrc committed to repo (ignore-scripts=true, save-exact=true, min-release-age=7)
  • [ ] npm ci --ignore-scripts in all CI pipelines (belt + suspenders)
  • [ ] No ^ or ~ in package.json dependencies
  • [ ] Don't install packages published < 7 days ago
  • [ ] npm audit --audit-level=high gates CI

If You've Already Been Hit

A developer whose team used APIFOX (also compromised this week) laid out what's actually at risk after a supply chain attack. It's not just your code:

Rotate immediately:

  • [ ] All SSH keys on every server
  • [ ] All API keys in .env files, CI/CD secrets, and config files
  • [ ] Exchange API keys — set withdrawal disabled + IP whitelist on every exchange
  • [ ] Browser sessions — sign out everywhere, clear cookies, revoke OAuth tokens
  • [ ] Any token that was ever in an environment variable or clipboard

Crypto-specific:

  • Private keys should never exist in plaintext on any server — if they do, assume compromised
  • Exchange API: attacker can drain funds via wash trading even with withdrawal disabled — monitor for unusual trading activity
  • SSH keys give attackers a way around IP whitelists — treat SSH key theft as full server compromise

For next time:

  • Local dev: only use test API keys, never production keys
  • SSH: use a passphrase + consider a bastion/jump host
  • Browser: enable 2FA/MFA on everything that touches money or infra

The attack surface isn't your code. It's everything your code touches.

Top comments (0)