Deploy Node.js securely: Continuous audit of dependencies

An important part of Node.js application life-cycle management is auditing for known vulnerabilities in dependencies. In this post, I discuss how and when to audit application dependencies for vulnerabilities.

npmjs.com maintains a list of security advisories and npm checks packages against that list of advisories during install.

An unfortunate aspect of these advisories is that just because a package is considered to have a vulnerability, that does not at all mean that it is vulnerable for all uses. It’s quite common for a package to have a vulnerability that only affects users of specific features in that package, or that only affect certain deployment scenarios.

It is possible to evaluate every vulnerability to see if it is relevant, and ignore the irrelevant ones, but this is not typical. The problems with this are two-fold:

  1. It is a lot of work.
  2. It’s possible to get it wrong.

Most enterprises take the same approach to packages with reported vulnerabilities that they do with compiler warnings: fix them, so you don’t have to wonder if they are a problem.

One interesting aspect of npm audit warnings when compared to other static analysis tools (linters, compiler warnings, etc.), is that the results are not guaranteed to be consistent over time. The list of advisories that npm audit draws from is dynamic, it is continuously updated, and a package that had no reported vulnerabilities against it an hour ago, could have one now. This makes it more difficult to work with than a typical static analysis tool, which can be run once in the CI/CD pipeline, and if it passes, does not need to be checked until the application is changed (or, possibly, its build environment changes, such as a Node.js version update).

Audit during development

npm audit is implicitly run every time npm install runs, and how to use it well covered by the npm audit docs. What is less clear is when to run it, and how.

As developers, if an npm install reports an audit warning, fix it. See the npm audit docs for how to do this.

Audit during continuous integration / continuous delivery (CI/CD)

CI should run audit checks, but be very careful about making them mandatory in a business critical CI/CD pipeline. If an audit warning is reported, but not fixable until dependencies publish updates to npmjs.com, your entire CI/CD pipeline could be blocked on external, third-party packages that have absolutely no responsibility to your company. This situation might be difficult to justify to your management and other stakeholders.

It is advisable to either have the npm audit results be advisory, or at least to ensure it is possible to force the CI/CD pipeline to complete even if audit fails.

With audit during CI/CD, it is important to realize that this is not sufficient. An application deployed with no audit warnings can have vulnerabilities reported against its dependencies in the future, and this needs to be dealt with.

Audit after deploy

There are a few ways to ensure that deployed applications do not have vulnerabilities reported against their dependencies.

One is to take advantage of third-party services that have access to your source repositories. There are a few options here:

There has been some consolidation in the market:

These services are advisory (they do not require being called from CI/CD pipelines), have notification capabilities, and are continuously evaluated as new vulnerabilities are found and reported. Notification is commonly in the form of a pull request, so that if CI is setup the update will be automatically tested to confirm it doesn’t introduce problems. It may be possible to specify multiple branches to test, if the application has multiple branches deployed, or a deployment branch that is not the same as the development branch.

It is not necessary to use these services to get continuous evaluation. It is also possible to set up a recurring CI job that does nothing but check out deployed application sources, and runs npm audit on it to test the current state. Since this test is not in the CI/CD pipeline, it should fail if the audit fails. Make sure to run against all the deployed branches as well as the development branches. With the DIY approach, you will have to do the updates yourself.

While the services are usually only free for open source projects, or at least, for public repositories, the DIY approach on top of npm audit is free, but involves more maintenance.

Updating your development dependencies

It’s possible to audit only production dependencies (npm audit --production).

This is an attractive option, because a vulnerability in a dependency of a dependency of your test runner, a package that isn’t even present on your production systems, is unlikely to be a huge concern.

Delaying fixing these dependencies is reasonable, and there is no reason to do a production redeploy because of a change in a development dependency.

However, don’t leave them forever. The npm install warnings are annoying, and like all warnings, once you start ignoring some, its easy to not notice a new production warning that appears (though the approaches for “audit after deploy” should find those).