Skip to main content

npm Supply Chain Attacks

A supply chain attack in the npm ecosystem occurs when an attacker compromises a package — or the infrastructure around it — to inject malicious code into applications that depend on it. npm is uniquely exposed to this class of attack: the registry hosts over 3 million packages, the average project pulls in hundreds of transitive dependencies, many packages are maintained by a single person, and npm install executes arbitrary install scripts by default. This page covers what has happened, how these attacks work, and what you can do to defend against them.

Why npm Is a Target

  • Scale — A typical frontend project depends on hundreds of transitive packages. Each one is a potential entry point. You don't need to compromise a top-level dependency — any package deep in the tree will do.
  • Trust modelnpm install runs preinstall and postinstall scripts by default. These scripts can execute arbitrary code on the developer's machine or CI runner with no sandboxing.
  • Single points of failure — Many widely-used packages are maintained by one person. One compromised account, one moment of burnout, one social engineering attempt — and malicious code flows into millions of downstream projects.
  • Low barrier to publish — Anyone can create an npm account and publish a package with any unclaimed name. There is no review process, no code signing requirement, and no waiting period by default.

Notable Incidents

These are real, high-profile incidents — not hypotheticals. They illustrate the patterns that keep repeating.

YearPackageWhat happenedImpact
2016left-padAuthor unpublished the package after an npm name disputeBroke thousands of builds worldwide; not malicious, but exposed the fragility of deep dependency trees
2018event-streamNew maintainer (gained access via social engineering) added the flatmap-stream dependency with a crypto-stealing payloadTargeted the Copay Bitcoin wallet
2021ua-parser-jsMaintainer's npm account hijacked; cryptominer and credential stealer injected~8M weekly downloads affected
2021coa, rcMaintainer account compromises; malware injected into popular packagesBroke React app builds globally
2022colors.js, faker.jsMaintainer deliberately added an infinite loop ("protestware") to protest unpaid open-source laborBroke thousands of dependent applications
2022node-ipcMaintainer added code to overwrite files on machines with Russian or Belarusian IP addressesProtestware with destructive payload
2024lottie-playerCompromised npm publish token; crypto wallet drainer injected into popular animation libraryAffected sites began prompting users to connect crypto wallets
2025@tanstack/*Compromised npm automation token; obfuscated data-exfiltration payload published to multiple packages via CI/CD pipelineMillions of weekly downloads affected; detected and remediated within hours
note

This table is not exhaustive. New incidents emerge regularly. The patterns are more important than the specific packages.

Attack Vectors

Typosquatting

The attacker registers a package with a name that looks almost identical to a popular one. A developer types npm install crossenv instead of npm install cross-env and gets malware. The crossenv typosquat was a real case in 2017 — it harvested environment variables (including npm tokens and AWS keys) and sent them to a remote server. Other examples include electorn (mimicking electron) and lodahs (mimicking lodash).

Typosquatting is effective because developers install packages quickly, often from memory or by copy-pasting partial names.

Dependency Confusion

The attacker publishes a public package on the npm registry using the same name as an internal or private package used within a company. If the package manager is not configured to resolve that name from the private registry first, it may pull the public (malicious) version instead — often because the public version has a higher semver number.

This technique was demonstrated by security researcher Alex Birsan in 2021, who successfully executed code inside the networks of Apple, Microsoft, PayPal, and dozens of other companies using this method. His research writeup is essential reading.

Account Takeover

The attacker compromises an npm maintainer's credentials — through a weak password, credential reuse from a breached database, or phishing — and publishes a new version of the legitimate package containing malicious code. From the consumer's perspective, nothing looks unusual: the package name is correct, the version number is incremented normally, and the publisher appears to be the rightful maintainer.

The ua-parser-js incident in 2021 is the textbook example. npm now supports and strongly encourages two-factor authentication, but it is not yet mandatory for all packages.

Social Engineering (Maintainer Grooming)

A contributor approaches a maintainer, submits helpful pull requests over weeks or months, builds trust, and eventually receives publish rights. Once they have access, they inject malicious code — either directly or through a new dependency they control.

The event-stream attack in 2018 is the canonical example. The original maintainer, burned out from maintaining a package they no longer used, handed over access to someone who had been contributing productively. That person then added a targeted payload designed to steal cryptocurrency from Copay wallet users.

Malicious Install Scripts

npm's preinstall and postinstall lifecycle scripts run arbitrary shell commands during npm install. An attacker can use these scripts to exfiltrate environment variables (which often contain API keys and tokens), install backdoors, download and execute remote payloads, or modify files on the system.

Most developers never inspect the install scripts of their dependencies. Even a careful review of the main source code would miss malicious behavior hidden in a postinstall step.

Protestware

A maintainer intentionally sabotages their own package for ideological or political reasons. The colors.js and faker.js incidents in 2022 were driven by frustration over unpaid open-source labor. The node-ipc incident the same year added code targeting machines based on their geographic IP address.

Protestware is technically legal — the maintainer owns the code and can modify it — but it is devastating downstream. It also erodes the trust model that the entire ecosystem depends on.

Starjacking

The attacker sets a package's repository field in package.json to point at a popular, unrelated GitHub repository. Tools and dashboards that display GitHub stars for npm packages then show the star count of the legitimate repo, making the malicious package appear well-established and trusted.

Starjacking is purely cosmetic deception. It does not compromise the referenced repository, but it makes basic due diligence less effective.

Prevention

Lock Files and npm ci

Always commit package-lock.json to version control. The lock file pins the exact version and integrity hash of every installed package. In CI, use npm ci instead of npm install — it installs from the lock file deterministically and fails if the lock file is out of sync with package.json.

.github/workflows/ci.yml
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npm test

npm audit

Run npm audit regularly and as part of your CI pipeline. It checks installed packages against the GitHub Advisory Database and reports known vulnerabilities.

Starting with npm v9.5, you can also run npm audit signatures to verify that packages were published through the npm registry's signing pipeline. This helps detect packages that were tampered with after publication.

caution

npm audit only catches known vulnerabilities — those that have already been reported and added to the advisory database. It will not detect a zero-day supply chain attack, a compromised maintainer publishing a new malicious version, or protestware. It is one layer of defense, not a complete solution.

Ignore Install Scripts

Disable install scripts globally by adding the following to your .npmrc:

.npmrc
ignore-scripts=true

This prevents preinstall, install, and postinstall scripts from running during npm install. Most packages do not need install scripts — but some do (e.g., packages with native bindings like esbuild or sharp). After enabling this setting, run your build to identify which packages need scripts, then handle those explicitly.

tip

Test your build after enabling ignore-scripts to identify which packages require scripts. Most projects work fine without them.

Pin Exact Versions

By default, npm install saves dependencies with a caret range (^1.2.3), which allows automatic minor and patch updates. To pin exact versions instead:

npm config set save-exact true

Or add this to your .npmrc:

.npmrc
save-exact=true

This ensures npm install some-package saves "some-package": "1.2.3" instead of "some-package": "^1.2.3". The trade-off is that you lose automatic patch updates — you will need to update dependencies explicitly using tools like Dependabot or Renovate.

Scoped Packages

If your organization uses internal packages, always publish them under an npm scope (@yourorg/package-name). Scopes are tied to an npm organization and cannot be claimed by anyone outside that organization.

This prevents dependency confusion attacks. Without a scope, an attacker can publish a public package with the same name as your internal package and potentially trick your package manager into resolving the public version.

pnpm minimumReleaseAge

If your project uses pnpm, the minimumReleaseAge setting blocks installation of packages published less than a specified time ago. This gives the community and automated scanners time to flag compromised packages before they reach your project. npm 11+ offers the equivalent min-release-age setting.

For more details on package managers, see the npm page.

npm min-release-age

Starting with npm CLI 11 (February 2026), npm natively supports a minimum release age quarantine via the min-release-age config option. Add it to your project's .npmrc:

.npmrc
min-release-age=7

The value is in days. npm will refuse to resolve any package version published less than 7 days ago, giving the community and automated scanners time to detect and flag compromised releases before they reach your project.

caution

Do not combine min-release-age with --before in the same invocation — npm errors out if both are present.

This is the npm-native equivalent of pnpm's minimumReleaseAge. For npm projects, prefer this setting.

Security Tooling

ToolWhat it doesCost
npm auditChecks installed packages against the GitHub Advisory DatabaseFree, built-in
Socket.devAnalyzes actual package behavior (network access, filesystem use, install scripts) — catches new threats before they are in advisory databasesFree for open source; paid for private repos
SnykVulnerability scanning, automated fix PRs, license compliance checkingFree tier; paid for teams
npm provenanceLinks a published package version to a specific source commit and build via SigstoreFree (npm v9.5+)
OpenSSF ScorecardRates open-source project security practices (branch protection, dependency updates, CI tests, etc.)Free

Dependency Hygiene

Good security is not just tooling — it is also habits:

  • Evaluate before installing. Check download counts, maintenance activity (recent commits, open issues), the number of transitive dependencies the package pulls in, and its OpenSSF Scorecard score.
  • Prefer packages with few or zero dependencies. Every transitive dependency is additional attack surface. When two packages offer similar functionality, prefer the one with a smaller dependency tree.
  • Remove unused dependencies. Run npx depcheck periodically to find packages listed in package.json that are not imported anywhere in your code.
  • Review package-lock.json diffs in PRs. Lock file changes show exactly which packages were added, removed, or updated. A PR that adds an unexpected dependency is worth investigating before merging.
  • Keep dependencies updated. Use Dependabot or Renovate to receive automated PRs for dependency updates. Outdated dependencies accumulate known vulnerabilities over time.

Further Reading