Why this matters
In 2025, Sonatype identified over 454,600 new malicious open-source packages across major ecosystems, and over 99% of them were on npm. The biggest attacks hit packages like chalk, debug, and nx through stolen credentials and phishing. In March 2026, even axios (100M weekly downloads) was hijacked.
Many of these attacks relied on malicious code that ran during npm install through lifecycle scripts (preinstall/postinstall), though some (like the chalk/debug compromise) injected runtime code instead. Most were detected within hours, but that was enough to cause damage.
This is my note on what to do about it.
Use pnpm
pnpm v10 has unusually strong supply-chain controls. Three settings in pnpm-workspace.yaml would have reduced exposure to many high-profile attacks from 2025-2026:
# Only install packages older than 7 days.
# Most malicious packages are caught and removed within hours.
minimumReleaseAge: 10080
# Block trust downgrades.
# If a package was published through verified CI/CD before,
# block versions published with a regular token.
trustPolicy: no-downgrade
# Block all lifecycle scripts by default.
# Whitelist only the packages that need them.
# (pnpm 10.26.0+, replaces deprecated onlyBuiltDependencies)
strictDepBuilds: true
allowBuilds:
"[email protected]": true
You can exclude your own packages from the age check:
minimumReleaseAgeExclude:
- "@myorg/*"
References:
- pnpm: Mitigating Supply Chain Attacks
- pnpm: minimumReleaseAge
- pnpm: trustPolicy
- pnpm: onlyBuiltDependencies
Pin exact versions
Add this to .npmrc:
save-exact=true
save-prefix=''
In pnpm v10, dependency lifecycle scripts are already blocked unless explicitly allowed via allowBuilds, so ignore-scripts=true is not needed. If you use npm, add ignore-scripts=true here as well.
This prevents surprise updates from semver ranges. Use Renovate or Dependabot for controlled, reviewable dependency updates.
Use frozen lockfiles in CI
Always use frozen lockfiles in CI so your pipeline installs exactly what is in your lockfile:
pnpm install --frozen-lockfile
Even if a malicious version is published, your CI will not pull it unless someone explicitly updates the lockfile.
Install Socket Firewall
Socket Firewall is a free tool that blocks known malicious packages at install time. It works as a proxy between your package manager and the registry.
npm i -g sfw
Make it permanent with a shell alias:
# Add to .zshrc or .bashrc
alias pnpm='sfw pnpm'
If you use GitHub, add the Socket for GitHub app. It monitors dependency changes in PRs and analyzes new dependency risk.
References:
Use Docker for installs
Running pnpm install inside Docker limits what a malicious package can access. By default, it cannot reach your SSH keys, cloud credentials, or home directory, but it can still read build context, environment variables, .npmrc tokens, and exfiltrate data over the network.
# Stage 1: install deps in isolation
FROM node:22-alpine AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
RUN corepack enable && pnpm install --frozen-lockfile
# Stage 2: build with source code
FROM node:22-alpine AS build
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build
Docker is a containment layer, not a prevention layer. Use it alongside the tools above, not instead of them.
Reduce your attack surface
A 2019 npm ecosystem study found the average package depends on 79 transitive packages from 39 maintainers. Every dependency is a potential risk.
Before adding a new package:
- Check if a native Node.js or Web API can do the same thing
- Check the package on npmx.dev for detailed analysis
- Look for the provenance badge on npmjs.com
- Check maintainer count and recent activity
The es-tooling/module-replacements repo lists npm packages that can be replaced with native alternatives.
If you publish packages
If your project publishes npm packages, you are also a potential target. Both the Nx and Axios compromises happened because attackers got access to publish tokens.
- Use npm Trusted Publishing (OIDC) to publish from CI/CD without long-lived tokens
- Require 2FA on your npm account
- Remove any
NPM_TOKENfrom CI workflows if you use Trusted Publishing (if both are present, npm uses the token, which defeats the purpose) - Prefer OIDC-based release pipelines over manual
npm publish
Other tools
- Aikido Safe Chain - wraps npm/pnpm/yarn/bun with extra security checks, free and open source
- Snyk - vulnerability scanning with CLI and CI/CD plugins
- StepSecurity - hardens GitHub Actions workflows
pnpm audit- built-in vulnerability scanning, useful but does not detect malware without a CVE
Quick reference
minimumReleaseAge: 10080
trustPolicy: no-downgrade
strictDepBuilds: true
allowBuilds:
"[email protected]": true
save-exact=true
save-prefix=''
# Install Socket Firewall
npm i -g sfw
# Use it
sfw pnpm install --frozen-lockfile
Further reading
Spot something wrong?
Found a typo, a broken link, or something that could be better? I would love to hear from you. Drop me a message and I will fix it.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.