Skip to main content
Dublin Library

The Publishing Project

NPM Security Best Practices

 

In recent months, we've seen a rise in security vulnerabilities related to the NPM ecosystem. As developers, it's crucial to follow best practices to ensure the security of our applications. This responsibility extends beyond just our own code; it includes maintaining the integrity of our dependencies.

This post will cover essential NPM security best practices, focusing on two key areas: understanding the supply chain threat and implementing a layered defense for your projects.

The Threat: Understanding Supply Chain Attacks #

NPM, like other package managers, is a common target for supply chain attacks. These attacks compromise popular packages to steal credentials, inject malware, and—because they target popular tools—spread rapidly across the ecosystem.

There are several common types of supply chain attacks:

Account Takeover
Attackers use methods like targeted phishing emails to trick a package maintainer into giving up their credentials. With this access, they bypass multi-factor authentication (MFA) and publish malicious new versions of popular packages.
Malicious Install Scripts
Many attacks abuse lifecycle scripts, particularly the postinstall script, which automatically runs code after a package is installed. This allows malicious code to execute on a developer's machine or in a CI/CD environment.
Dependency Confusion
Attackers publish a malicious package to the public NPM registry with the same name as a private, internal package used by a company. If the company's build system is misconfigured, it may download the public, malicious version instead of the internal, legitimate one.
Typosquatting
Attackers publish malicious packages with names very similar to popular ones (e.g., lodashs instead of lodash). Developers may install these by mistake and compromise their systems.
Self-Propagating Worms
Malware like the "Shai-Hulud" worm can replicate itself. Once a compromised package is installed, the worm steals the victim's credentials and uses them to inject malicious code into other packages owned by the same developer, creating an exponential spread.

Real-World Examples #

  • Shai-Hulud Worm (September 2025): CISA issued an alert about this self-replicating worm, which compromised hundreds of NPM packages. The malware stole developer and cloud credentials by injecting malicious scripts and GitHub Actions workflows, weaponizing the victim's own development infrastructure.
  • Chalk and Debug (September 2025): Attackers used a phishing email to take over a maintainer's account for popular packages like debug and chalk. They injected cryptocurrency-stealing malware, which, though live for only a few hours, could potentially affect billions of weekly downloads.

The Solution: A Layered Defense #

Understanding the threats is the first step. The second is building a layered defense that combines project configuration, CI/CD safeguards, and secure developer habits.

Project & Dependency Management #

Use Lockfiles and Pin Versions
Always commit your lockfile (package-lock.json, pnpm-lock.yaml). This ensures reproducible builds and that every developer and build server uses the exact same dependency versions. In CI environments, always use npm ci, which strictly installs only from the lockfile, rather than npm install.
Audit and Reduce Your Dependencies
The most secure dependency is one you don't have. Regularly audit your project and remove unused dependencies. You can use tools that perform reachability or call-graph analysis to identify dead code and unused packages.
Minimize Software Reuse
For trivial packages (e.g., a simple utility function), consider copying the code directly into your project. This removes the upstream dependency and its associated supply chain risk.
Standardize — you don’t need two PDF creation libraries in one project.

Runtime & Execution Security #

Disable Lifecycle Scripts by Default
Since many attacks rely on postinstall scripts, you should disable them globally: npm config set ignore-scripts true. If you must run a script for a trusted package, you can enable it for a single installation: npm install <package-name> --ignore-scripts=false.
Package managers like pnpm already do this by default. This "opt-in" approach significantly reduces your attack surface.
Integrate Automated Scanning
Run Software Composition Analysis (SCA) and malware scanning in your CI/CD pipeline. These tools detect known vulnerabilities and help you maintain an up-to-date Software Bill of Materials (SBOM). Since supply chain risk can only be mitigated, not eliminated, these detective controls are critical for a fast incident response.

Developer & Account Security #

Secure Your NPM Account
Enforce Multi-Factor Authentication (MFA) for all NPM activities. When publishing packages from CI/CD, use OIDC tokens or granular access tokens instead of long-lived, static credentials to reduce the risk of token theft.
Delay Dependency Updates
A new version isn’t automatically a better one. Attackers rely on developers updating immediately without vetting the new code.
Implement a "cooldown period" of several days before upgrading to give the community time to discover and report malicious releases. You can use settings in tools like Dependabot or use NPM's --before flag to manage the cooldown.
Watch for Red Flags
Monitor your dependencies for unusual activity.
Be wary if a small, stable, package suddenly ships a huge change, adds a new postinstall script, or changes maintainers without explanation. Investigate anything that feels "off."

Conclusion #

The NPM ecosystem is a powerful tool, but it comes with significant security challenges. By understanding the threats, securing your accounts, and implementing a layered defense for your projects, you can significantly reduce the risk of vulnerabilities in your applications.

References #

Edit on Github