How to avoid vulnerability to malicious modules distributed through npm repository

; Date: June 18, 2019

Tags: Node.JS

Recently a pair of modules in the npm repository became the vector for distributing malicious code. As much as npm Inc is working to improve security in the npm repository, there are steps we all must take as Node.js software coders to reduce the vulnerability of our packages. While its unlikely, in theory malicious code could be introduced through any package and how would we notice? It's not enough to trust npm Inc, we have our own work to do to strengthen the modules we publish.

For details on the specific vulnerability see Regarding the recent security vulnerability in event-stream and other npm packages

What follows is some advice and discussion.

Do not use wildcard package dependencies

In the malicious packages seen so far, the malicious payload was distributed by depending on wildcard package dependencies.

Namely - it is common practice to use a dependency like ^3.3.5 or ~3.3.5. These two mean:

  • ~version "Approximately equivalent to version" See npm help 7 semver
  • ^version "Compatible with version" See npm help 7 semver

There are many other wildcard dependency specifiers, e.g. >=3.3.x. The bottom line is that these are conveniences, but it means our end users will be installing a package which we did not test against.

In the case of the event-stream attack the malicious code was introduced in version 3.3.6, and therefore any application depending on ^3.3.5 will have automatically upgraded to 3.3.6.

The cure? Do not use wildcard dependency specifiers of any kind.

Corollary - Carefully test when upgrading dependencies

A wildcard dependency specifier is bad software engineering practice even if it does not introduce malicious code.

If package A uses a wildcard dependency on package B - the package A maintainer will have tested the package against a specific version of package B. But when package B releases what it claims to be a compatible version, users of package A will automatically update to that new version, but package A was not tested against that version.

It means there is a potential for package A to simply break because package B's maintainer was sloppy about claiming a new release was compatible.

By using a static package dependency (3.3.5 instead of ^3.3.5), when we test our package before releasing it we are certain our users will use only the code we tested against.

Be conservative about adopting dependencies

Any 3rd party package is, in this scenario, a potential attack vector. By reducing the number of dependencies we reduce the attack surface of the package we distribute.

This recommendation is also not solely about security threats. Reducing the package dependencies means our node_modules directory is smaller, and it takes less time to install our package. And it reduces the time we spend on verifying the 3rd party dependencies.

Use npm audit

The npm Inc team is making an investment in automatically screening packages published to the npm repository. One channel for exposing the resulting data is the npm audit command.

A database of known vulnerabilities is maintained by npm Inc, and any package that depends on code with a known vulnerability causes a warning message to be printed. HEED THESE WARNINGS.