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
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
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"
^ 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
- [ ]
.npmrccommitted to repo (ignore-scripts=true,save-exact=true,min-release-age=7) - [ ]
npm ci --ignore-scriptsin all CI pipelines (belt + suspenders) - [ ] No
^or~inpackage.jsondependencies - [ ] Don't install packages published < 7 days ago
- [ ]
npm audit --audit-level=highgates 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
.envfiles, 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)