Supply-chain security guide

What is npm typosquatting and how to detect it

An attacker publishes a package called crossenv. You mean to install cross-env. One missing hyphen later, attacker code runs on your machine — before your editor even opens. This guide covers how typosquatting works, documented examples, and the verification steps that stop it.

How the attack works

npm has no review gate. Anyone can publish a package with any name that isn't already taken. That openness — which is also what made npm's ecosystem grow so fast — means an attacker can publish lodahs the same afternoon lodash became popular.

The attack works in two stages:

  1. Wait for a typo. Developers mistype package names — especially when following a tutorial, copying from a Stack Overflow answer, or typing quickly in a terminal. Popular packages with hyphens (cross-env, babel-cli), common suffixes (-js, -node), or long names are the easiest targets.
  2. Execute at install time. npm runs the scripts.postinstall field in package.json automatically when a package is installed — with no confirmation prompt, no sandbox, and the full permissions of the current user. A postinstall script can read files, make network requests, and access every environment variable in the current shell session.
The timing matters. Code from a postinstall script runs during npm install, not when you later import the package. By the time you see your prompt return, any credential exfiltration has already completed — even if you immediately delete the package.

The typical payload reads environment variables like AWS_ACCESS_KEY_ID, OPENAI_API_KEY, or the npm token stored in ~/.npmrc, then sends them to an external server via a single HTTP request. The attacker's package itself may look completely innocuous — it might even re-export the real package's API so the rest of your code works fine and nothing looks broken.

Documented examples

These are not hypotheticals. Typosquatting attacks on npm have been documented and reported by security researchers since at least 2017.

The 2017 crossenv campaign

Security researcher Nick Sweeting documented a batch of packages — including crossenv (targeting cross-env), babelcli (targeting babel-cli), socket.io.js, and several others — that were published specifically to mimic popular packages. Each ran a postinstall script to steal npm tokens and environment variables from developer machines. npm removed them after the report, but not before they had accumulated real installs.

Ongoing automated campaigns

Since 2017, npm security (and independent researchers) have continued to discover and remove typosquatted packages. The pattern has stayed consistent: an attacker watches the npm download charts, picks the top 100–200 most-installed packages, and publishes variations on each name. Automated tooling has made both the attack and the detection faster — npm now scans for name similarity at publish time, but the checks are heuristic and not exhaustive.

What npm audit will not catch

Checknpm auditName similarity scan (DepCheck)
Package has a published CVE advisory✓ Yes✗ No
Package name is similar to a popular package✗ No✓ Yes
Package was removed from npm after a report✗ No — already ran✗ No — already ran
Package has a suspicious postinstall script✗ No✗ No — inspect manually
Publisher account looks new or single-package✗ No✗ No — inspect manually

The table makes the key point clear: npm audit and typosquatting detection solve different problems. A typosquatted package that hasn't been formally reported to npm's advisory database is invisible to npm audit. Catching it requires a name-similarity check and manual inspection before the package is installed.

Three red flags in a package

No single signal is definitive on its own — a legitimate package with a short name can look suspicious. The combination of all three is what makes a package high-risk.

Name similarity

The package name is one character different from a popular package: a transposition (lodahs), a missing or extra character (expres, expresss), a missing hyphen (crossenv), or a common suffix (lodash-js).

Unexpected install script

The package's package.json has a scripts.postinstall, scripts.preinstall, or scripts.install field. Many legitimate packages use postinstall scripts, but they should be explainable — if the script shells out to an external URL or reads home-directory files, stop.

Publisher signals

The publisher account was created recently, has published only one or two packages, and the package has few or no weekly downloads despite being published for some time. The GitHub repository link is missing or points to a fork of the real project.

Before you install: 5 checks

These five steps take under two minutes and completely eliminate the install-time risk for new packages. Make them a habit for any package you haven't installed before.

  1. Spell the name on npmjs.com before you type it in the terminal. Search for the package name directly on npmjs.com. The real cross-env has millions of weekly downloads. If the package you find has hundreds, it's worth a closer look at who published it.
  2. Read the postinstall script before it runs. On the npmjs.com package page, click "Code" or look for a link to the GitHub repository. Open the package's package.json and find the scripts object. Is there a postinstall entry? If so, read what it does before running npm install. You can also use npm pack to inspect a package without installing it:
    # Download without running any install scripts
    npm pack crossenv
    # Then inspect the tarball
    tar -tzf crossenv-*.tgz
  3. Check the publisher account. On the npmjs.com page, click the publisher's username. How many packages have they published? How long has the account been active? A package maintainer who has published a single package one week ago and whose profile has no avatar or bio is a yellow flag.
  4. Verify the repository link. The real cross-env links to github.com/kentcdodds/cross-env. A typosquatted package might link to a fork, a personal copy, or no repository at all. The repository link is in the package metadata on npmjs.com. If it's missing or points somewhere unexpected, don't install.
  5. Use npm info before npm install. npm info fetches registry metadata without running any scripts. It shows you the description, maintainers, homepage, and repository in one output before anything touches your machine:
    npm info cross-env
    Compare the output to what you expect. If the description doesn't match the real package, the maintainer list looks unfamiliar, or the homepage is empty, stop and re-verify the name.

Audit your existing package.json

If you've been running npm install for a while without checking names, it's worth a one-time audit of your existing dependencies. There are two ways to do this.

Option A: Scan with DepCheck (recommended for full sweeps)

Paste your package.json into DepCheck. It compares every package name in your manifest against a database of popular packages and flags any name that is suspiciously similar — a single-character transposition, an added suffix, or a missing delimiter. You get a severity-sorted report covering typosquatting risk alongside CVEs, abandoned packages, and license issues.

Scan your package.json for typosquatting risk →

Option B: Manual spot check on a specific package

If you want to check one package you're unsure about:

# Fetch registry metadata (no scripts run)
npm info <package-name>

# Compare to the real package
npm info cross-env
npm info crossenv   # if this exists, compare the two outputs side-by-side

If both exist, look at: (1) the download counts — npm info shows weekly downloads; (2) the maintainers list; (3) the repository field. Any significant divergence between the two is worth investigating.

Check your lockfile too. package-lock.json records the exact resolved version and its integrity hash (sha512) for every package in your tree — including transitive dependencies your package.json never mentions directly. Reviewing a lockfile diff on any PR that adds a new dependency shows exactly what was resolved and from where.

Team enforcement

Individual checks work. But a team of five developers installing packages independently creates five separate attack surfaces. These measures extend the protection to the whole team.

Commit and review the lockfile on every dependency change

Require package-lock.json to be committed and require it to appear in PRs any time a dependency is added or changed. A lockfile review is much faster than a code review — you're looking at name, version, and integrity hash — and it catches anyone who installed something off-script.

Use npm ci in CI (not npm install)

npm ci installs exactly what is in the lockfile. It will not resolve a different version of a package due to a range match, and it fails if the lockfile is out of sync with package.json. This closes the window where a typosquatted package could be silently resolved in CI even if the lockfile looks correct locally.

Block unexpected install scripts with --ignore-scripts

For projects where none of your dependencies legitimately need postinstall scripts, you can disable them entirely:

npm install --ignore-scripts

This prevents any postinstall, preinstall, or install script from running — which blocks the primary attack vector of a typosquatted package, even if one slips through a name check. The caveat: some packages use postinstall legitimately (native addons, binary downloads). Check whether your specific dependency set requires it before making this the default.

# Set as a project-level default in .npmrc
ignore-scripts=true
Lockfile confusion attacks. A more sophisticated variant of supply-chain attack targets the lockfile directly: an attacker social-engineers a contributor into committing a modified package-lock.json that points to a malicious resolved URL for a legitimate package name. Protect against this by verifying lockfile changes in code review and using npm's built-in integrity checks (npm ci verifies the sha512 hash of every downloaded package against the lockfile entry).

Keep the full pre-deploy checklist

Typosquatting is one of the six risk categories that the full pre-deploy audit guide covers — alongside CVEs, abandoned packages, license risk, loose version ranges, and dependency bloat. A complete audit runs all six before every deployment.

Frequently asked questions

What is npm typosquatting?

An attack where a malicious package is published under a name nearly identical to a popular one — a missing hyphen, a transposed letter, an added suffix. Developers who mistype the real name during npm install install the attacker's version. The malicious package runs code at install time via a postinstall script, typically stealing environment variables, API keys, or npm tokens. The code runs before your project ever imports the package.

Does npm audit catch typosquatted packages?

No. npm audit checks packages against the npm advisory database for published CVE advisories. A typosquatted package that hasn't been formally reported yet — which is most of them, since the window between publication and removal can be days or weeks — won't appear in npm audit output at all. Detecting typosquatting requires a name-similarity check before install, not an advisory lookup after.

How do typosquatted packages steal credentials?

Via the scripts.postinstall field in package.json. npm automatically runs this script when a package is installed, with the current user's full permissions and access to all environment variables in the shell session. A postinstall script can read ~/.npmrc (which stores your npm token), environment variables like AWS_ACCESS_KEY_ID or DATABASE_URL, and files in your home directory — and send them to an external URL via a simple HTTP request. The exfiltration completes during npm install, not at runtime.

Are there documented examples of npm typosquatting attacks?

Yes. The 2017 crossenv campaign (reported by Nick Sweeting) involved multiple packages — crossenv, babelcli, socket.io.js, and others — published to mimic popular packages and steal npm tokens via postinstall scripts. Since then, security researchers and npm's own team have continued to find and remove new campaigns. The attack is not theoretical — it has resulted in real credential theft from developer machines.

How do I check if a package has a postinstall script before installing it?

Use npm pack <package-name> to download the package tarball without running any scripts. Then extract it and read the package.json inside. Alternatively, view the package on npmjs.com and navigate to the repository link to read the package.json directly in GitHub. Look for a scripts object with postinstall, preinstall, or install entries.

What is the difference between typosquatting and dependency confusion?

Typosquatting exploits developer typing errors — a mistyped name resolves to the attacker's package. Dependency confusion (also called namespace confusion) exploits registry resolution behavior — an attacker publishes a public package with the same name as your company's private internal package, and npm's resolution order causes the public malicious version to be installed instead. Both attack vectors run code at install time, but they require different defences: typosquatting is stopped by name verification, dependency confusion by scoped packages and explicit registry configuration.

Does blocking postinstall scripts with --ignore-scripts break things?

It depends on your dependencies. Some packages use postinstall legitimately — native Node.js addons (like node-sass or packages using node-gyp) compile binaries at install time; some packages download pre-built platform-specific binaries; some run one-time setup steps. If none of your dependencies require postinstall, adding ignore-scripts=true to .npmrc is a low-cost security improvement. Test it in a clean install first to confirm nothing breaks.

Also in the Copper Bay Labs ship-safety suite

Dependencies are one layer of your security posture. These free tools cover the rest before you ship: