TL;DR
NPM supply chain attacks spiked to over 3,000 malicious packages in 2024. The 2026 Axios compromise proved even top-10 packages are targets. This guide gives API developers practical, actionable defenses: lockfile enforcement, postinstall script blocking, provenance verification, behavioral analysis, and architectural choices to reduce your attack surface.
Introduction
The Axios supply chain attack (March 31, 2026) wasn’t the first npm compromise—and won’t be the last. With 83 million weekly downloads and a cross-platform RAT deployed via a single hijacked maintainer, it was a major wake-up call for the JavaScript ecosystem.
This attack bypassed all traditional defenses:
- Malicious code wasn't in Axios itself.
- It used a phantom dependency and a postinstall hook.
- Lockfiles didn’t help if you ran
npm installduring the attack window. - Version pinning didn’t help if you hadn’t already pinned.
API developers are especially exposed—your CI/CD, tests, mock servers, and HTTP clients all install from npm. Any compromised package in your toolchain can leak API keys, database credentials, or cloud tokens from your dev machine.
💡 Apidog eliminates one major attack vector by providing a built-in HTTP client for API testing. You don’t need Axios, node-fetch, or got in your testing stack. Download Apidog free to reduce your npm dependency surface while following the defense strategies below.
This guide covers seven layers of protection, from lockfile hygiene to advanced behavioral analysis.
Layer 1: Lockfile Enforcement
Why lockfiles matter
A lockfile records the exact version of every package and transitive dependency at installation. Without it, npm install resolves the latest version matching your semver range. If your package.json has "axios": "^1.14.0" and a malicious 1.14.1 appears, you get the malicious version.
The rules
1. Always commit your lockfile.
Whether it’s package-lock.json (npm), yarn.lock (Yarn), pnpm-lock.yaml (pnpm), or bun.lock (Bun), put it in version control.
2. Use frozen installs in CI/CD.
Never run npm install in automation. Use these instead:
# npm
npm ci
# yarn
yarn install --frozen-lockfile
# pnpm
pnpm install --frozen-lockfile
# bun
bun install --frozen-lockfile
npm ci deletes node_modules and installs exactly from the lockfile, failing if mismatched. No surprises.
3. Review lockfile diffs in PRs.
If a PR changes package-lock.json, review the diff. New dependencies, version bumps, and registry URL changes all require scrutiny. Tools like Socket.dev can automate flagging suspicious changes.
The lockfile gap
Lockfiles don’t protect against the first install. If you add a new dependency or initialize a project during an attack window, the malicious version gets locked in. That’s why lockfiles are only Layer 1.
Layer 2: Disable Postinstall Scripts
The primary attack vector
The Axios attack, ua-parser-js, event-stream, and many others used the same trick: a postinstall script that runs arbitrary code during npm install—before you see the code or run your app.
Block scripts globally
Add to your .npmrc:
ignore-scripts=true
Or via CLI:
npm config set ignore-scripts true
This blocks all lifecycle scripts (preinstall, install, postinstall, prepare) during install.
Handling packages that need scripts
Some packages (e.g., bcrypt, sharp, sqlite3) require postinstall scripts for compilation. Options:
Option 1: Run scripts only after install
npm ci --ignore-scripts
npm rebuild bcrypt sharp
Option 2: Use an allowlist (npm 10+)
Create .scriptsrc.json:
{
"allowScripts": {
"bcrypt": true,
"sharp": true
}
}
Option 3: Use prebuilt binaries
Many packages now provide prebuilt binaries (like sharp), so check if you can avoid native compilation.
The PackageGate caveat
In Jan 2026, "PackageGate" zero-days showed: Git-based dependencies can include config files that enable code execution even when lifecycle scripts are disabled. If you use Git URLs for dependencies, pin to specific commit hashes and audit the repo.
Layer 3: Pin Exact Versions
Stop using semver ranges
Default npm install --save adds packages with a caret:
{
"axios": "^1.14.0"
}
The ^ means “compatible with 1.14.0”—so you'll get the latest 1.x.x, even if it's malicious.
Pin exact versions:
{
"axios": "1.14.0"
}
Set npm to always save exact versions:
# .npmrc
save-exact=true
save-prefix=''
Use overrides for transitive dependencies
Direct dependencies have their own dependencies. If a transitive dep is compromised, pinning your direct dep isn’t enough.
For npm:
{
"overrides": {
"axios": "1.14.0",
"plain-crypto-js": "npm:empty-npm-package@1.0.0"
}
}
For Yarn:
{
"resolutions": {
"axios": "1.14.0"
}
}
For pnpm:
{
"pnpm": {
"overrides": {
"axios": "1.14.0"
}
}
}
The trade-off
Exact pinning blocks automatic patch updates. You'll need to manually bump versions—extra maintenance, but vital for high-security projects.
Layer 4: Verify Package Provenance
What provenance means
Npm provenance attestation links a published package to its source code and build environment using Sigstore signatures. When enabled, you get cryptographic proof of:
- Source repository
- CI/CD system
- Commit that triggered build
How to check provenance
npm audit signatures
This verifies installed packages’ provenance. Malicious Axios versions lacked OIDC provenance and GitHub commits. Automated provenance checks would have flagged the attack.
Enable provenance for your own packages
In your CI/CD, enable provenance:
# GitHub Actions example
- uses: actions/setup-node@v4
with:
node-version: 20
registry-url: https://registry.npmjs.org
- run: npm publish --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
Add to .npmrc:
provenance=true
Limitations
Provenance is opt-in (most packages don’t have it yet). It proves where a package was built, but not that the code is safe—a compromised CI/CD can still publish malicious code.
Layer 5: Use Behavioral Analysis Tools
Beyond vulnerability scanning
Tools like npm audit and Snyk only catch known vulnerabilities (CVEs). They miss zero-days like the Axios compromise.
Behavioral analysis tools examine what packages do—not just public vulnerability reports.
Socket.dev
Socket analyzes behavior during install and runtime, flagging:
- Network requests during install
- File system access outside package directory
- Shell command execution
- Environment variable harvesting
- Obfuscated code
With GitHub integration, Socket comments on PRs if new dependencies behave suspiciously. The Axios attack’s plain-crypto-js would have triggered multiple Socket alerts.
# Install Socket CLI
npm install -g @socketsecurity/cli
# Scan your project
socket scan
Snyk
Snyk provides vulnerability management for known issues.
# Install Snyk CLI
npm install -g snyk
# Test your project
snyk test
Layered approach
Use all three:
# Baseline
npm audit
# Behavioral analysis
socket scan
# Vulnerability management
snyk test
Run these in CI/CD. Any critical finding should block the build.
Layer 6: Minimize Your Dependency Surface
The deeper question
Every package in node_modules is a trust decision. Axios was compromised with 83 million weekly downloads. The average Node.js project has hundreds of transitive dependencies—each a potential attack vector.
Fewer dependencies = less to defend.
Audit your dependency tree
# Count total dependencies
npm ls --all | wc -l
# Check for duplicates
npm ls --all | sort | uniq -c | sort -rn | head -20
For each dependency, ask:
-
Does Node.js provide this natively now? Node.js 18+ includes
fetch,crypto,URL,FormData, etc. - Does this dependency pull dozens of transitive deps? One package can multiply your attack surface.
- Can you vendor this? For tiny utilities, copy source into your repo.
Native alternatives for common packages
| Package | Native alternative | Available since |
|---|---|---|
| axios, node-fetch, got |
fetch (global) |
Node.js 18 |
| uuid | crypto.randomUUID() |
Node.js 19 |
| dotenv |
--env-file flag |
Node.js 20.6 |
| chalk | util.styleText() |
Node.js 21.7 |
| glob | fs.glob() |
Node.js 22 |
| path-to-regexp | Native URL pattern API | Node.js 23 |
For API testing specifically
API testing often brings in HTTP clients, assertion libs, test runners, and mock servers—each a dependency and attack surface.
Apidog replaces the whole stack with a single platform:
- HTTP client: Built-in. No npm dependency.
- Assertions: Visual builder with built-in assertions.
- Test runner: Automated scenarios with CI/CD via Apidog CLI.
- Mock server: Dynamic responses, no Express or third-party mock libraries.
- Documentation: Auto-generated from your API specs.
Move API testing into Apidog to eliminate dozens of npm dependencies. Fewer dependencies = fewer attack vectors.
Try Apidog free to consolidate your API testing stack.
Layer 7: Network and Runtime Monitoring
Block known-bad domains
After a supply chain attack, block C2 infrastructure at the network level:
# Add to /etc/hosts
echo "0.0.0.0 sfrclak.com" | sudo tee -a /etc/hosts
In CI/CD, restrict outbound network access to only allowed domains (npm registry, your git host, deployment targets).
Use StepSecurity Harden-Runner for CI/CD
StepSecurity’s Harden-Runner monitors GitHub Actions workflows, detecting network calls, process exec, and file changes in real time. It caught the Axios dropper’s C2 call within 1.1 seconds.
- Outbound network monitoring
- Process execution tracking
- File integrity monitoring
- Alerts for anomalous behavior
# GitHub Actions
- uses: step-security/harden-runner@v2
with:
egress-policy: audit # or 'block' for strict mode
Runtime process monitoring
For development machines, use endpoint detection & response (EDR) tools to flag suspicious child processes from Node.js. The Axios RAT spawned osascript (macOS), cscript (Windows), and python3 (Linux) from npm install—these are detectable patterns.
Recommended .npmrc Configuration
A security-hardened .npmrc:
# Pin exact versions
save-exact=true
save-prefix=
# Disable lifecycle scripts
ignore-scripts=true
# Enable provenance for publishing
provenance=true
# Use the official registry
registry=https://registry.npmjs.org/
# Require 2FA for publishing
auth-type=web
# Audit level threshold
audit-level=moderate
Commit this file to your repo so everyone uses the same settings.
CI/CD Security Pipeline Example
A GitHub Actions workflow enforcing all seven layers:
name: Secure Build
on: [push, pull_request]
jobs:
security-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: step-security/harden-runner@v2
with:
egress-policy: audit
- uses: actions/setup-node@v4
with:
node-version: 22
# Layer 1+2: Frozen lockfile, no scripts
- run: npm ci --ignore-scripts
# Layer 3: Verify no unexpected versions
- run: npm ls --all > deps.txt
# Layer 4: Check provenance
- run: npm audit signatures
# Layer 5: Behavioral analysis
- run: npx socket scan
# Layer 5: Vulnerability scan
- run: npx snyk test
# Layer 1: Baseline audit
- run: npm audit --audit-level=moderate
# Rebuild only allowed native deps
- run: npm rebuild sharp bcrypt
What’s Coming Next for npm Security
Mandatory provenance for popular packages
Npm may soon require provenance attestation for high-download packages, blocking manual publishing methods that enabled the Axios attack.
Two-person release approval
Packages with massive downloads may need a second maintainer’s approval for releases—raising the bar for attackers.
Runtime permission scoping
Deno restricts script permissions (network, file system, env vars) unless explicitly allowed. Node.js is exploring similar models. When released, scripts like postinstall will need explicit permissions.
Package manager convergence
pnpm’s strict isolation (packages can only access declared deps) blocks many confusion attacks. Expect npm to adopt similar strictness.
FAQ
What is a supply chain attack in npm?
A supply chain attack targets your dependencies—not your application directly. Attackers compromise maintainer accounts, inject code into packages, or publish typosquats. When you install or update, malicious code runs on your machine or CI/CD, stealing credentials or installing backdoors.
Is npm audit enough to protect against supply chain attacks?
No. npm audit checks public vulnerability databases (CVEs), but zero-days like Axios aren’t listed when they happen. Use behavioral tools like Socket.dev as well. npm audit is the baseline—not your only defense.
Should I stop using npm entirely?
No. npm is the largest package ecosystem and most packages are safe. Reduce exposure with version pinning, lockfile enforcement, script blocking, and minimizing dependencies. Prefer native APIs when possible.
How does Apidog help reduce npm supply chain risk?
Apidog provides a built-in HTTP client, test runner, mock server, and documentation generator for API dev. You don’t need Axios, node-fetch, Jest, Express, or other testing dependencies—so you have fewer npm attack vectors.
What is package provenance in npm?
Provenance uses Sigstore to cryptographically link a package to its source repo and CI/CD build. You can verify with npm audit signatures. Packages published manually lack provenance—a red flag for high-download packages.
How many npm packages are malicious?
Snyk found 3,000+ malicious npm packages in 2024. By Q4 2025, Sonatype blocked over 120,000 malware attacks in package registries. Most are low-download typosquats, but incidents like Axios show even top packages are at risk.
What is the PackageGate vulnerability?
PackageGate is a set of six zero-days (Jan 2026) affecting npm, pnpm, vlt, and Bun. Git-based dependencies can enable code execution even with lifecycle scripts disabled. Pin Git deps to commit hashes and audit contents.
Key Takeaways
- Commit and enforce lockfiles—but they only protect after first install.
-
Disable postinstall scripts globally with
ignore-scripts=truein.npmrc. -
Pin exact versions using
save-exact=trueto avoid semver range surprises. -
Verify provenance with
npm audit signaturesto catch manual uploads. -
Layer Socket.dev (behavior analysis) and Snyk (known vulnerabilities) on top of
npm audit. - Reduce dependency count with Node.js native APIs and integrated platforms like Apidog.
- Monitor CI/CD network egress with StepSecurity Harden-Runner.
Every dependency is a trust decision. Fewer dependencies = smaller attack surface. More verification layers = harder for attackers. Build defense in depth, not isolation.



Top comments (0)