In June 2026, 144 packages in the Mastra namespace were compromised by a single attacker mass-publishing malicious variants. In October 2025, a batch of malicious JetBrains Marketplace plugins with 70,000 installs were found exfiltrating API keys. These aren’t unusual events: the npm registry logs tens of thousands of package removal requests per year for malicious or suspicious packages. The volume of malicious package attempts continues to increase because the payoff is high: a successful postinstall script on a developer machine, or in a CI/CD pipeline, can exfiltrate credentials, modify build output, or persist backdoors.
The good news is that practical defences exist and don’t require significant tooling investment.
Understanding the Attack Patterns
Before choosing defences, it’s worth being clear about what you’re defending against:
Typosquatting: Publishing a package with a name visually similar to a popular one. lodash vs 1odash, axios vs axois. These rely on developers not noticing the misspelling or IDEs autocompleting to the wrong name.
Dependency confusion: Publishing a public package with the same name as a private internal package. npm’s resolution previously preferred the higher version number from any registry — attackers publish public packages at version 9999.0.0 to override internal packages fetched from private registries.
Compromised maintainer accounts: Legitimate packages acquired or taken over by attackers through credential theft or npm account abandonment. The ua-parser-js and node-ipc incidents demonstrated this pattern at scale.
Postinstall script abuse: npm packages can run arbitrary scripts during npm install via postinstall in package.json. This is used legitimately (native module compilation), but malicious packages use it to immediately run code at install time, before the package is ever imported.
Namespace squatting: Registering common variations of package namespaces before the legitimate owner does, then publishing confusing packages under them.
Tool 1: npm audit
npm audit is the baseline. It checks your installed dependencies against the npm advisory database for known vulnerabilities:
npm audit # Full audit report
npm audit --audit-level=high # Exit non-zero only for high/critical
npm audit --json # Machine-readable output for CI
npm audit fix # Auto-fix where possible
npm audit fix --force # Fix including breaking changes (review carefully)
npm audit tells you about known vulnerabilities in published packages. It does not detect novel malicious packages or those that haven’t been flagged yet. Use it as one layer, not the whole defence.
In CI/CD:
# GitHub Actions step
- name: Run npm audit
run: npm audit --audit-level=high
# Fails the pipeline if high or critical vulns are found
Tool 2: Socket.dev — Detecting Malicious Behaviour
Socket.dev analyses package behaviour rather than just known CVEs. It flags packages that:
- Install scripts that make outbound network requests
- Read environment variables (potential credential exfiltration)
- Write to filesystem locations outside their package directory
- Have unusual binary executables included
- Show rapid ownership changes (potential account takeover)
# Install Socket CLI
npm install -g @socket/cli
# Analyse a package before installing it
socket scan npm install PACKAGE_NAME
# Run a full scan on current project
socket npm audit
Socket’s free tier covers open-source projects; paid plans support private registries. Their CI/CD integration provides a pull request check that blocks packages with malicious indicators before they enter the codebase.
Tool 3: Lockfiles and npm ci
The npm lockfile (package-lock.json) pins exact versions AND records the integrity hash (SHA-512) for every installed package. npm ci uses the lockfile exclusively, never upgrading anything, and verifies the integrity hash of every package against what was recorded when the lockfile was generated.
# Always use npm ci in CI/CD pipelines — not npm install
npm ci
# Generate or update lockfile
npm install # Updates package-lock.json with current resolutions
Critical: commit package-lock.json to source control. A missing or gitignored lockfile means your CI/CD pipeline can silently install different package versions than developers tested locally.
If a lockfile entry’s integrity hash doesn’t match the downloaded package, npm ci will fail the installation:
npm error `sha512-HASH` integrity checksum failed when using sha512:
wanted sha512-EXPECTED but got sha512-ACTUAL
This catches both corrupted downloads and cases where a package version has been tampered with post-publication (rare but has occurred).
Tool 4: Verifying Signatures with npm audit signatures
npm introduced package provenance and signing in 2023. For packages published with the --provenance flag, you can verify the package was built from the claimed source repository:
npm audit signatures
This checks the signature chain for all installed packages. Output shows which packages have verifiable signatures and which don’t. Packages lacking signatures aren’t necessarily malicious, but packages with signature verification failures warrant investigation.
Blocking Postinstall Scripts
The highest-risk npm attack vector is the postinstall script. If you cannot verify that a package’s install scripts are safe, blocking them prevents code execution at install time:
# Install without running any scripts
npm install --ignore-scripts
# Or set permanently in .npmrc
echo "ignore-scripts=true" >> .npmrc
The tradeoff: some legitimate packages (native modules like bcrypt, sharp, better-sqlite3) require postinstall compilation to function. With ignore-scripts, you’ll need to explicitly compile them:
# Run build scripts for specific packages after --ignore-scripts install
npm rebuild bcrypt
npm rebuild better-sqlite3
For most web application projects with no native module dependencies, --ignore-scripts adds no friction and eliminates the entire postinstall code execution surface.
Detecting Dependency Confusion
Configure npm to use your private registry for internal package namespaces and prevent fallback to the public registry:
# .npmrc — scope your internal packages to your registry
@your-org:registry=https://your-registry.example.com/
//your-registry.example.com/:_authToken=${NPM_TOKEN}
# Explicitly block public resolution for internal scopes
@your-org:registry=https://your-registry.example.com/
@your-org:always-auth=true
With this configuration, any package in the @your-org scope will only resolve from your private registry and never fall back to npmjs.com — even if an attacker publishes a higher-versioned package there.
For packages without an @org scope (legacy internal package names), the protection is harder. Prefer migrating them to a scoped namespace.
Generating an SBOM
A Software Bill of Materials lets you track what dependencies you’re actually shipping and check them against vulnerability databases:
# Generate SPDX SBOM from npm
npm sbom --sbom-format=spdx --sbom-type=package > sbom.spdx.json
# Generate CycloneDX format
npx @cyclonedx/cyclonedx-npm --output-file sbom.cdx.json
Run SBOM generation in your release pipeline and archive the output. If a new vulnerability is disclosed, you can query historical SBOMs to determine which of your deployed versions are affected without re-running the build.
Checklist for New Dependencies
Before adding a new npm package to a project, a 90-second check covers most obvious risks:
- Check npm download count: Is it genuinely used? Packages with 50 weekly downloads posing as popular utilities are suspicious
- Check publication date and author history: A package registered two days ago by a brand-new account is higher risk
- Review package.json scripts: Does it have
preinstall,install, orpostinstallscripts? What do they do? - Check the repository URL: Does it link to a real, active repository? Does the code match what’s in the registry?
- Run Socket scan:
socket scan npm install PACKAGE_NAMEbefore installing
# Quick pre-install check
npx npm-audit-extra --check-scripts PACKAGE_NAME
# Or use Socket
socket scan npm install PACKAGE_NAME
These steps take less time than reading a package’s README and catch the vast majority of obviously malicious packages. For high-trust pipelines — CI/CD that deploys to production — combining lockfile integrity, npm ci, postinstall blocking, and Socket scanning provides defence-in-depth against all current npm attack patterns short of a compromised legitimate maintainer.