Open source packages - and npm specifically - are undoubtedly awesome. They make developers extremely productive by giving each of us a wealth of existing functionality just waiting to be consumed. If we had to write all this functionality ourselves, we'd struggle to create a fraction of what we do today.
As a result, a typical Node.js application today consumes LOTS of npm packages, often hundreds or thousands of them. What we often overlook, however, is that each of these packages, alongside its functionality, also pulls in its Node.js security risks. Many packages open new ports, thus increasing the attack surface. Roughly 76% of Node shops use vulnerable packages, some of which are extremely severe; and open source projects regularly grow stale, neglecting to fix security flaws.
Inevitably, using npm packages will expose you to security risks. Fortunately, there are several questions you can ask which can reduce your risk substantially. This post outlines these questions, and how to get them answered.
#1: Which packages am I using?
The more packages you use, the higher the risk of having a vulnerable or malicious package amongst them. This holds true not only for the packages you use directly but also for the indirect dependencies they use.
Discovering your dependencies is as easy as running
npm ls in your application’s parent folder, which lists the packages you use. You can use the
--prod argument to only display production dependencies (which impact your security the most), and add
--long to get a short description of each package. Check out this post to better understand how you can slice and dice your npm depedencies.
Figure: Inventorying Node’s
redis few dependencies
Now that you know what you have, you can ask a few questions to assess the risk each package involves. Below are a few examples of questions you should ask, why you should ask them, and suggestions on how you can get them answered.
#2: Am I still using this package?
As time goes by and your code changes, you’re likely to stop using certain packages and add new ones instead. However, developers don’t typically remove a package from the project when they stop using it, as some other part of the code may need it.
As a result, projects have a tendency to accumulate unused dependencies. While not directly a security concern, these dependencies unnecessarily grow your attack surface and add clutter to the code. For instance, an attacker may trick one package into loading an unused package with a more severe vulnerability, escalating the potential damage.
Checking for unused dependencies is most easily done using the
depcheck scans your code for
import commands, correlate those with the packages installed or mentioned in your package.json, and provide a report. The command can be tweaked in various ways using command flags, making it easy to automate checking for unused dep’s.
~/proj/Hardy $ depcheck Unused dependencies * cucumber * selenium-standalone Unused devDependencies * jasmine-node
Figure: Checking for unused dependencies on the Hardy project
#3: Are other developers using this package?
Packages used by many are also more closely watched. The likelihood of someone having already encountered and addressed a security issue on them is higher than in a less utilized package.
For example, the
secure-compare package was created to support string comparison that was not susceptible to a timing attack. However, a fundamental flaw in the package led to achieving the exact opposite, making certain comparisons extremely time sensitive (and incorrect).
If you looked more closely, you’d see this package is very lightly used, downloaded only 20 times a day. If this were a more popular package, odds are someone would have found and reported the functional flaw sooner.
The easiest way to assess package usage is its download rate, indicated in the “Stats” section of the npm’s package page. You can extract those stats automatically using the npm stats API, or browse historic stats on npm-stat.com. Alternatively, you can look at the number of “Dependent” packages – other packages that use the current one.
#4: Am I using the latest version of this package?
Bugs, including security bugs, are constantly found and – hopefully – fixed. Also, it’s quite common to see newly reported vulnerabilities fixed only on the newest major branch of a project.
For instance, in early 2016, a Regular Expression Denial of Service (ReDoS) vulnerability was reported on the HMAC package
hawk. ReDoS is a vulnerability where a long or carefully crafted input causes a regular expression match to take a very long time to compute. The processing thread does not serve new requests in the meantime, enabling a denial of service attack with just a small number of requests.
The vulnerability in
hawk was quickly fixed in its latest major version stream, 4.x, but left older versions without a fix. Specifically, it left an unfixed vulnerability in the widely used
request package, which used email@example.com. The author later accepted Snyk’s pull-request with a fix for the 3.x branch, but
request users were exposed for a while, and the issue still exists in the older major release branches. This is just one example, but as a general rule, your dependencies are less likely to have security bugs if they’re on the latest version.
You can find out whether or not you’re using the latest version using the
npm outdated command. This command also supports the
--prod flag to ignore dev dependencies, as well as
--json to simplify automation. You can also use Greenkeeper to proactively inform you when you're not using the latest version.
~/proj/handlebars.js $ npm outdated --prod Package Current Wanted Latest Location async 1.5.2 1.5.2 2.0.1 handlebars source-map 0.4.4 0.4.4 0.5.6 handlebars uglify-js 2.6.2 2.7.3 2.7.3 handlebars yargs 3.32.0 3.32.0 5.0.0 handlebars
Figure: npm outdated on
handlebars prod dependencies
#5: When was this package last updated?
Creating an open source project, including npm packages, is fun. Many talented developers create such projects in their spare time, investing a lot of time and energy in making them good. Over time, however, the excitement often wears off, and life changes can make it hard to find the needed time.
As a result, npm packages often grow stale, not adding features and fixing bugs slowly – if at all. This reality isn’t great for functionality, but it’s especially problematic for security. Functional bugs typically only get in your way when you’re building something new, allowing some leeway for how quickly they’re addressed. Fixing security vulnerabilities is more urgent – once they become known, attackers may exploit them, and so time to fix is critical.
A good example of this case is a Cross-Site Scripting vulnerability in the
marked package. Marked is a popular markdown parsing package, downloaded nearly 2M times a month. Initially released in mid-2011, Marked evolved rapidly over the next couple of years, but the pace slowed in 2014 and work stopped completely in mid-2015.
The XSS vulnerability was disclosed around the same time, and it has remained untouched ever since. The only way to protect yourself from the issue is to stop using
marked, or use a Snyk patch, as explained below.
Inspecting your packages for their last update date is a good way to reduce the change you’ll find yourself in such a predicament. You can do so via the npm UI or by running
npm view <package> time.modified.
$ npm view marked time.modified 2016-07-30T03:10:20.053Z
Figure: checking last modified time on
#6: How many maintainers do these packages have?
Many npm packages only have a single maintainer, or a very small number of those. While there’s nothing specifically wrong with that, those packages do have a higher risk of getting abandoned. In addition, larger teams are more likely to have at least some members who better understand and care more about security.
Identifying the packages that have only a few maintainers is a good way to assess your risk. Tracking npm maintainers is easily automated by using
npm view <pkg> maintainers.
$ npm view express maintainers [ 'dougwilson <firstname.lastname@example.org>', 'hacksparrow <email@example.com>', 'jasnell <firstname.lastname@example.org>', 'mikeal <email@example.com>' ]
Figure: maintainers of the
express package, per npm
However, many packages with a full team behind them publish automatically through a single npm account. Therefore, you’ll do well to also inspect the GitHub repository used to develop this package (the vast majority of npm packages are developed on GitHub). In the example above, you'll find there are 192 contributors to the
express repo. Many only made one or two commits, but that's still quite a difference from the 4 listed npm maintainers.
You can find the relevant GitHub repository by running
npm view <pkg> repository, and then subsequently run
curl https://api.github.com/repos/<repo-user>/<repo-name>/contributors. For instance, for the
marked package, you would first run
npm view marked repository, and then
curl https://api.github.com/repos/chjj/marked/contributors. Alternatively, you can easily see the maintainers, GitHub repository and its contributors via the npm and GitHub web UI.
#7: Does this package have known security vulnerabilities?
The questions above primarily reflect the risk of a future problem. However, your dependencies may be bringing in some security flaws right now! Roughly 15% of packages carry a known vulnerability, in either in their own code or in the dependencies they in turn bring in. According to Snyk's data, about 76% of Node shops use vulnerable dependencies in their applications.
You can easily find such vulnerable packages using Snyk. You can run
snyk test in your terminal, or quickly test your GitHub repositories for vulnerable dependencies through the web UI. Snyk's test page holds other testing options.
Snyk also makes it easy to fix the found issues, using
snyk wizard in the terminal or an automated fix pull request. Fixes are done using guided upgrades or open source patches. Snyk creates these patches by back-porting the original fix, and they are stored as part of its Open Source vulnerability database.
Once you're free of vulnerabilities, you should ensure code changes don't make you vulnerable again. If you're using Snyk, you can test if pull requests introduce a vulnerable dependency, or add a test such as
snyk test to your build process.
Lastly, when a new vulnerability is disclosed, you'd want to learn about it before attackers do. New vulnerabilities are independently of your code changes, so a CI test is not sufficient. To get an email (and a fix pull request) from Snyk whenever a new vulnerability affects you, click "Watch" on the "Test my Repositories" page, or run
snyk monitor when you deploy new code.
Solving Node.js Security
npm packages are amazing, and let us build software at an unprecedented pace. You should definitely keep using npm packages - but there's no reason to do so blindly. We covered 7 questions you can easily answer to understand better, and reduce your security exposure:
- Which packages am I using? And for each one...
- Am I still using this package?
- Are other developers using this package?
- Am I using the latest version of this package?
- When was this package last updated?
- How many maintainers do these packages have?
- Does this package have known security vulnerabilities?
Answer those, and you'll be both productive and secure!
If you have any thoughts or questions on the topic, please share them in the comments.