Today, Feross Aboukhadijeh (founder of Socket) flagged an active supply chain attack on axios — one of npm's most depended-on packages with 100M+ weekly downloads. The latest axios@1.14.1 pulled in plain-crypto-js@4.2.1, a package that didn't exist before that day. It was textbook supply chain malware: an obfuscated dropper that deobfuscates payloads at runtime, dynamically loads fs, os, and execSync to evade static analysis, executes shell commands, stages payloads in temp directories, and deletes artifacts post-execution to destroy forensic evidence.
Every npm install pulling the latest version was potentially compromised. The advisory was simple: pin your version immediately, audit your lockfiles, do not upgrade.
But "do not upgrade" is not a long-term strategy. Dependencies need updates — security patches, bug fixes, new features. The question isn't whether to update, it's how to update safely. The answer is quarantine: run every dependency update in an isolated sandbox, observe its behavior, validate it passes your tests, and only then promote it to your real environment.
This post walks through how to build that quarantine workflow with Clawstainer.
The Problem: npm install Is a Trust Exercise
When you run npm install (or pip install, or cargo update), you're executing arbitrary code from the internet on your machine. Install scripts run with your user's permissions. They can read your filesystem, your environment variables, your SSH keys, your AWS credentials. They can phone home. They can install backdoors.
The axios attack is not an anomaly. Supply chain attacks have hit:
- event-stream (2018) — compromised maintainer, stole cryptocurrency
- ua-parser-js (2021) — hijacked npm account, installed crypto miners
- colors / faker (2022) — maintainer protest, broke thousands of apps
- xz utils (2024) — multi-year social engineering attack on a Linux compression library
- axios (2026) — obfuscated dropper, 100M+ weekly downloads exposed
The pattern is always the same: a trusted package gets compromised, and because everyone auto-upgrades, the blast radius is enormous. Lockfiles help — they pin exact versions — but the moment you update that lockfile, you're trusting again.
The Quarantine Pattern
The idea is straightforward. Never run npm install with new dependency versions on a machine you care about. Instead:
- Snapshot your current known-good environment
- Spin up a disposable sandbox from that snapshot
- Run the update inside the sandbox
- Observe, test, and audit
- If clean, promote the update. If not, destroy the sandbox and nothing was harmed.
The sandbox is isolated. It has its own filesystem, its own PID namespace, its own network namespace. If a malicious install script tries to read your SSH keys, stage payloads in temp directories, or phone home to a C2 server — it does all of that inside a container that you're about to destroy. Your actual machine is untouched.
Step 1: Snapshot Your Known-Good State
Start by creating a sandbox that mirrors your project's environment. Install your language runtime, your tools, clone your repo, install your current pinned dependencies:
# Create a sandbox with your project's requirements
clawstainer create --name project-base --memory 2048 --cpus 2
# Provision with the tools you need
clawstainer provision <id> --components nodejs,git,ripgrep
# Copy your project into the sandbox
clawstainer cp ./my-project <id>:/root/my-project
# Install current pinned dependencies (the ones you trust)
clawstainer exec <id> "cd /root/my-project && npm ci"
Verify everything works:
# Run your test suite
clawstainer exec <id> "cd /root/my-project && npm test"
If tests pass, snapshot this state. This is your known-good baseline:
clawstainer snapshot create <id> --name project-clean
Step 2: Quarantine the Update
Now when you want to update dependencies, never do it on your host machine. Spin up a disposable sandbox from the snapshot:
# Disposable quarantine box — boots in 2-3 seconds with everything already installed
clawstainer create --name quarantine --from project-clean --memory 2048 --cpus 2
Run the update inside the quarantine:
# Update a specific package
clawstainer exec <id> "cd /root/my-project && npm install axios@latest"
# Or update everything
clawstainer exec <id> "cd /root/my-project && npm update"
If axios@1.14.1 had been the latest version, the malicious plain-crypto-js dropper would have executed its install script inside the sandbox. It would have tried to stage payloads in /tmp and ProgramData — inside the sandbox. It would have executed shell commands — inside the sandbox. It would have tried to phone home — through the sandbox's network, which you can monitor or cut entirely.
Your host machine? Untouched.
Step 3: Audit and Observe
With the update installed in quarantine, now you investigate.
Run your test suite
clawstainer exec <id> "cd /root/my-project && npm test"
If tests fail, something changed. Investigate or destroy.
Audit what changed in the dependency tree
# What new packages appeared?
clawstainer exec <id> "cd /root/my-project && npm ls --all 2>&1 | head -50"
# Diff the lockfile against the known-good version
clawstainer exec <id> "cd /root/my-project && diff package-lock.json.bak package-lock.json"
In the axios attack, this is where you'd see plain-crypto-js@4.2.1 appear in the tree — a package that didn't exist yesterday, pulled in by a package that never needed a crypto dependency before. That alone is a red flag.
Check for suspicious filesystem activity
# What files were created or modified outside node_modules?
clawstainer exec <id> "find /tmp -type f -newer /root/my-project/package.json 2>/dev/null"
clawstainer exec <id> "find /root -name '*.sh' -newer /root/my-project/package.json 2>/dev/null"
# Any new binaries or scripts in unexpected places?
clawstainer exec <id> "find /usr/local/bin -newer /root/my-project/package.json 2>/dev/null"
The axios dropper staged files in temp directories and deleted them post-execution. In a sandbox, you can snapshot before and after the install and diff the entire filesystem if you want to.
Check for network activity
# Install network monitoring tools
clawstainer exec <id> "apt-get install -y net-tools lsof"
# What's listening?
clawstainer exec <id> "lsof -i -P -n 2>/dev/null | grep ESTABLISHED"
# DNS lookups that happened during install (if you have logging)
clawstainer exec <id> "cat /var/log/syslog 2>/dev/null | grep -i dns | tail -20"
Or go nuclear: isolated network
For maximum paranoia, create the quarantine sandbox with no network access at all. Copy the update tarball in manually:
# Create an isolated sandbox (no NAT, no internet)
clawstainer create --name quarantine-strict --from project-clean --memory 2048 --cpus 2 --net isolated
# Download the package on your host first
npm pack axios@1.14.1
# Copy it into the sandbox
clawstainer cp axios-1.14.1.tgz <id>:/root/my-project/
# Install from the local tarball
clawstainer exec <id> "cd /root/my-project && npm install ./axios-1.14.1.tgz"
Now the install script can't phone home at all. If it tries to execSync('curl ...'), it fails. If it tries to exfiltrate data, it has nowhere to send it. You can inspect the installed files at your leisure.
Step 4: Promote or Destroy
If the update looks clean — tests pass, no suspicious files, no unexpected dependencies, no network anomalies — promote it:
# Copy the updated lockfile back to your host
clawstainer cp <id>:/root/my-project/package-lock.json ./my-project/package-lock.json
# Destroy the quarantine box
clawstainer destroy <id>
If anything looks wrong, destroy the sandbox and report the issue:
# Nuke it
clawstainer destroy <id>
# Your host is clean. Your production is clean. Nothing escaped.
The snapshot is still there. You can spin up another quarantine box in seconds to investigate further, try a different version, or test a patch.
Automating the Quarantine
For teams that update dependencies regularly, this can be scripted. Here's the basic flow:
#!/bin/bash
# quarantine-update.sh — test dependency updates in a sandbox
SNAPSHOT="project-clean"
QUARANTINE_ID=$(clawstainer create --name quarantine-$(date +%s) \
--from $SNAPSHOT --memory 2048 --cpus 2 --format json | jq -r '.id')
echo "Quarantine sandbox: $QUARANTINE_ID"
# Run the update
clawstainer exec $QUARANTINE_ID "cd /root/my-project && npm update"
# Run tests
TEST_RESULT=$(clawstainer exec $QUARANTINE_ID "cd /root/my-project && npm test" 2>&1)
TEST_EXIT=$?
if [ $TEST_EXIT -eq 0 ]; then
echo "Tests passed. Auditing..."
# Check for new unexpected dependencies
NEW_DEPS=$(clawstainer exec $QUARANTINE_ID \
"cd /root/my-project && npm ls --json 2>/dev/null" | \
jq '[.. | .dependencies? // empty | keys[]] | unique | length')
echo "Total dependencies: $NEW_DEPS"
# Check for suspicious temp files
SUSPICIOUS=$(clawstainer exec $QUARANTINE_ID \
"find /tmp -type f 2>/dev/null | wc -l")
echo "Files in /tmp: $SUSPICIOUS"
# Copy updated lockfile if everything looks clean
echo "Review complete. Promoting lockfile."
clawstainer cp $QUARANTINE_ID:/root/my-project/package-lock.json \
./my-project/package-lock.json
else
echo "Tests FAILED. Update rejected."
echo "$TEST_RESULT"
fi
# Always destroy the quarantine box
clawstainer destroy $QUARANTINE_ID
echo "Quarantine destroyed."
Run this in CI, in a cron job, or manually whenever Dependabot opens a PR. The quarantine box lives for minutes at most. Your host never runs untrusted code.
Why Not Just Use Docker?
You could. But Clawstainer sandboxes are lighter than Docker containers for this use case:
| Clawstainer | Docker | |
|---|---|---|
| Boot from snapshot | 2-3 seconds | Varies (layer cache dependent) |
| Snapshot workflow | Built-in (snapshot create/list/delete) |
docker commit + manual tagging |
| File copy in/out | clawstainer cp |
docker cp |
| Exec into running sandbox | clawstainer exec |
docker exec |
| Network isolation | --net isolated |
--network none |
| No daemon required | Yes (systemd-nspawn) | No (dockerd must be running) |
| Resource stats | clawstainer stats |
docker stats |
The real advantage is the snapshot workflow. You snapshot your known-good state once, and every quarantine box boots from that snapshot with your entire project, all dependencies, and all tools already in place. No Dockerfile to maintain, no layer caching surprises, no rebuilds.
Beyond npm: Python, Rust, Go
This pattern isn't npm-specific. Any package manager that runs arbitrary code during install is a vector:
# Python — pip install runs setup.py, which can execute anything
clawstainer exec <id> "cd /root/my-project && pip install -r requirements-new.txt"
# Rust — build.rs scripts run at compile time
clawstainer exec <id> "cd /root/my-project && cargo update && cargo build"
# Go — no install scripts, but you still want to test before promoting
clawstainer exec <id> "cd /root/my-project && go get -u ./... && go test ./..."
The quarantine pattern is the same for all of them: snapshot the clean state, update in isolation, test, audit, promote or destroy.
What This Doesn't Solve
Quarantining catches runtime behavior — malicious install scripts, suspicious file creation, unexpected network activity. It doesn't catch everything:
- Dormant payloads — malware that waits days or weeks before activating won't trigger during a short quarantine window
- Logic bugs — a backdoor that only fires on specific input won't show up in a test suite unless you test that input
- Subtle data exfiltration — if the malware phones home through DNS or encodes data in HTTP headers to legitimate-looking domains, basic network monitoring might miss it
Quarantine is one layer of defense. Pair it with lockfile auditing, tools like Socket for static analysis of packages, and monitored staging environments for time-delayed threats.
The Takeaway
The axios attack is a reminder that npm install is not safe by default. Neither is pip install. Neither is any command that downloads and executes code from a public registry. The packages you depend on can be compromised at any time, by anyone who gains access to a maintainer account or slips a PR past review.
You can't stop supply chain attacks from happening. But you can stop them from reaching your machine. Snapshot your known-good state. Run every update in a quarantine sandbox. Test it, audit it, and only then let it through. If it's dirty, destroy the sandbox and walk away clean.
The cost is a few extra minutes per update cycle. The alternative is explaining to your users why their data was exfiltrated because you ran npm update on a Tuesday.