npm Security Best Practices for Safe Installs

php2shell patches (CVE-2026-109618 and CVE-2026-109619) for EOL PHP are out now. With sales replies slower than usual due to high demand, the fastest route to patched packages is to buy ELS for Runtimes online (opens in new tab). Read the patch FAQ

Go straight to an AI summary of this blog post instead

  • Summarize this post with Perplexity
  • Summarize this post with Gemini

Every npm install is an act of trust. The same package ecosystem that expedites JavaScript development is also a primary vector for supply chain attacks. When you execute an install command, you are bringing in a sprawling tree of dependencies, many of which are obscure and hidden.

npm install is safe most of the time. That “most” is doing a lot of work. Install is not merely a download, it is an execution, and dependency lifecycle scripts run with whatever permissions the installing environment already has. On a developer laptop or a CI runner, that is usually plenty. The code is fetched, unpacked, and running before anyone has reviewed it. Everything below reflects npm v12, pnpm 11, and Yarn 4.10 as of August 2026, because the defaults in this area have moved fast.

The Typosquatting Trap

This attack occurs when an attacker publishes a package that has a typo in it. The typo might be a simple misspelling of the package name, or a different formatting trick entirely. It looks nearly identical to popular packages and replicates the same functionality.

  • Character Manipulation: Swapping lodash for lodahs, or using homoglyphs (e.g., replacing ‘l’ with ‘I’).
  • Format variations: Using hyphen tricks (cross-env vs. crossenv) or pluralization (color vs. colors).
  • Namespace/Scope confusion: Creating malicious scoped packages that mimic official libraries.

These packages can run arbitrary code during the install process, and they exfiltrate all types of creds from API keys, ENV vars to AWS creds, etc. This can happen extremely fast, before a developer has a chance to review the code.

How Dependency Confusion Attacks Work

This attack exploits organizations running a hybrid setup, an internal registry alongside the public npm registry.

Dependency Confusion attacks occur when a company has an internal package and an attacker publishes one with the same name to the public registry. They will usually publish it with a version number that outranks the internal one. If your build environment can resolve that name from both registries, it may pull the attacker’s public package instead. In that package is the malicious code.

Whether it happens comes down to your registry, scope, proxy, and lockfile configuration. The setups that get hit are the ones nobody audited.

The way to mitigate this risk is to use scoped packages and make the routing deterministic. Map each private scope explicitly to your private registry in .npmrc, and make sure nothing in your proxy chain silently falls back to public. Claiming the matching scope on npmjs.com adds a second layer, because an attacker cannot publish under a scope you already own. If you have installed anything in the past few years, you know what a scoped package is. A scoped package looks something like this, @my-org/cool-package. The first character is the @ symbol, followed by your name or org and the package you are installing.

Without those safeguards, scoping alone won’t completely stop the attack. A scope by itself is a naming convention, not a control. Making sure that your CI/CD platform is configured properly also helps in mitigating the risk.

What Happens When a Maintainer Account Gets Compromised

Typosquatting and dependency confusion both need you to install something you did not mean to install. The harder version skips that step. The attacker takes over a package you already trust, already reviewed, and already pinned.

This is the vector doing the most damage right now. A phishing campaign against npm maintainers in September 2025 produced the Shai-Hulud worm, which stole cloud tokens and used them to spread to further accounts. On August 4, 2026 an attacker got into the GitHub account of the maintainer behind keyv, a library with well over 100 million weekly downloads, and published credential-stealing releases across keyv, cacheable, flat-cache, file-entry-cache, and others in that cluster. Trackers counted hundreds of affected package names and over a thousand affected versions within a day.

Here is the part that should bother you. The release went out through the maintainer’s own GitHub Actions workflow, so it carried valid OIDC-backed provenance and a valid integrity hash. Both described exactly the poisoned artifact the legitimate workflow produced. Provenance establishes where and how a package was built. It says nothing about who was holding the keys.

Behavioral tooling and release-age controls caught this one, not cryptographic checks. Provenance is worth having, it is just not the finish line.

Why Transitive Dependencies Go Unaudited

A direct dependency, even though audited, might bring along plenty of transitive dependencies you haven’t seen and are not aware of. These hidden dependencies can be 5-10+ layers deep within the dependency tree.

Most dependencies reside deep within the project tree, where they are rarely audited manually, creating a “dark cavern” fully exposed to attackers. This lack of visibility is a primary vector for supply chain attacks. They occur when malicious actors infiltrate packages or inject them entirely, often going unnoticed during installation, a risk even some AI-driven scaffolding tools fail to address, as noted in my Dockerfile blog post.

While npm ls is a useful tool for visualizing complete dependency trees, it does not scan for vulnerabilities or suspicious behavior. That is why Software Composition Analysis (SCA) tooling matters for finding problems nested deep within the stack.

Tool Good at Will miss
Dependabot Known CVEs in your tree, automated bump PRs Brand new malicious packages, because no CVE exists yet
Socket Behavioral signals, new install scripts, unexpected network or filesystem access Not built as a CVE database first
Snyk CVE coverage plus supply chain and license checks Needs tuning before the output stops being noise

Dependabot is the low-friction baseline if your repos are on GitHub. Just do not mistake it for malicious-package detection, because a package published an hour ago has no CVE to match against.

How Malicious preinstall and postinstall Scripts Execute Code

preinstall and postinstall hooks in package.json execute code automatically once a package has been fetched and unpacked, before the developer has reviewed the package contents.

These scripts target high-value assets like banking systems, build servers, etc. The cream of the crop, so to speak. They exfiltrate vital credentials, such as .env files, AWS tokens, and SSH keys, that we are constantly reminded NOT to commit to version control.

Some scripts perform environment checks to detect sandboxes or heavily monitored systems before executing their payload, targeting less-hardened environments first.

This is still the delivery mechanism of choice. Every infected version in the August 2026 campaign carried a preinstall dropper.

The defaults finally moved, though. pnpm stopped running dependency lifecycle scripts by default in v10, and npm v12 shipped on July 8, 2026 with the same posture, managed through npm approve-scripts. An unapproved script is skipped with a warning, so set strict-allow-scripts if you want CI to fail instead. On npm 11 or earlier, the flag does that work for you:

bash

npm install --ignore-scripts

Worth knowing that none of this touches import-time execution. A compromised package whose top-level code runs when your app calls require() bypasses install-script controls entirely.

Why Package Age Is a Security Signal

Most of these attacks do get caught, usually within hours rather than weeks. The problem is that your CI already pulled the bad version during those hours.

A minimum release age narrows that window without asking anyone to audit anything. Every major package manager ships it now. pnpm 11 turns minimumReleaseAge on by default at 1,440 minutes, Yarn 4.10 defaults npmMinimalAgeGate to three days, and npm has min-release-age from v11.10 onward but leaves it off, so you set it yourself. Watch the units, because npm counts days and pnpm counts minutes.

# .npmrc

min-release-age=7

Three to seven days is where most practitioners land. It does nothing about a package that was malicious from day one, or one that stays dormant past your threshold.

npx needs the same thought. When it fetches something new, your lockfile does not govern which version it picks, so pin what you invoke with npx [email protected].

How to Install npm Packages Safely

  • Make sure you commit your lock files and use npm ci for deterministic, reliable builds in CI environments. The lockfile plus npm ci is what actually fixes your resolved tree, including transitive dependencies.
  • Pin exact versions in package.json by removing prefix symbols (^, ~) where you want to control upgrade timing on direct dependencies. That buys predictability rather than safety, since it does nothing for the transitive tree.
  • Let the package manager block dependency scripts by default. That is already the behavior on pnpm 10 and later and on npm v12, and it is –ignore-scripts on anything older.
  • Audit your packages regularly and use SCA tools like Dependabot, Socket, or Snyk to catch weirdness in your dependency tree.
  • Turn on a minimum release age of three to seven days.
  • Turn on MFA for every account that can publish, and keep publishing tokens narrow and short-lived.

Blocking scripts has a real cost, so decide deliberately. Native modules and browser automation tools need their install steps, and sharp, bcrypt, Prisma, Playwright, and Cypress are the usual casualties. In CI, block by default, approve only what you need, and run those builds somewhere that does not carry your production credentials. On a laptop, approve the one package that breaks rather than switching protection off for everything. Watch for silent failures either way, because a blocked native build can exit clean and only surface later as a missing module at runtime.

Closing Thoughts on npm Supply Chain Security

Software Supply Chain Security is never a one-and-done ops requirement. The practice of security is ongoing and should always be top of mind. Three things that are put on the back burner until all hell breaks loose are performance, accessibility, and – you guessed it – security. Keeping abreast of tools and tricks to help keep you secure is part of being a developer. Keep practical defenses in mind and always keep security at the forefront of shipping.

 

Summary
npm Security Best Practices for Safe Installs
Article Name
npm Security Best Practices for Safe Installs
Description
Every npm install runs code. Learn how typosquatting, dependency confusion, and hijacked maintainer accounts work, and how to install packages
Author
Publisher Name
TuxCare
Publisher Logo

Join 4,500+ Linux & Open Source Professionals!

2x a month. No spam.

Table of Contents
Get the open-source security answers you need

Join 4,500+ Linux & Open Source Professionals!

2x a month. No spam.