11 Nov, 2019

Making Sense Out of NPM Audit

by Alex Useche

So you are a frontend developer, or perhaps you work for your company’s internal application security team, and you decide it’s time to evaluate the security of your application’s NPM dependencies. You jump into the terminal, navigate to the root of your repository, and run npm audit. Then you see something like this:

NPM audit results

If you are a developer, you may start quietly breaking a sweat as you imagine having to figure out how to fix this mess of 616 vulnerabilities. A stream of questions may start flooding your mind: “how do we even have that many dependencies?” or “what will we need to break to fix this?” or even “should we ignore this for a bit longer?”. If you are an application security engineer, you may even think “hah! I told them this app was covered with vulnerable dependencies!” or maybe “the developers are going to hate me when I show them this.” While dependency vulnerabilities are certainly critical (and if you are not convinced, take a look at this article), the reality is often much less scary than it may seem. This is simply because remediation of vulnerabilities reported by NPM is often not as complicated as you may think when seeing the alarming results of the NPM audit utility. I am not saying that you shouldn’t be alarmed. Yet, by understanding what NPM audit is reporting, you may be able to communicate both the vulnerabilities and remediations to your team more effectively.

First of all, what does NPM audit do? Simply, it checks the versions of each dependency declared in a package-lock.json file against a database of vulnerabilities that may exist for those versions. However, to do its job well, it also checks recursively for the dependencies of your dependencies (those that you installed by running npm -i fancy-lib) and checks whether publicly known vulnerabilities exist for those. This is where the numbers start to add up. When npm audit tells you that it found 616 vulnerabilities, that doesn’t necessarily mean that all 616 vulnerabilities exist in the 10 packages you decided to use for your application. Let’s take a look at one of the vulnerabilities reported by NPM:

┌───────────────┬──────────────────────────────────────────────────────────────┐
│ High          │ Machine-In-The-Middle                                        │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ https-proxy-agent                                            │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ @angular/cli [dev]                                           │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ @angular/cli > @schematics/update > pacote >                 │
│               │ npm-registry-fetch > make-fetch-happen > https-proxy-agent   │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://nodesecurity.io/advisories/1184                      │
└───────────────┴──────────────────────────────────────────────────────────────┘

The above shows a High vulnerability in the https-proxy-agent package. The path section in the table tells us that the vulnerability in question exists because the Angular CLI library that you are using has a dependency called @schematics/update. That dependency, in turn, uses a dependency called pacote, which leverages npm-registry-fetch and so on, until we reach the vulnerable library npm-registry-fetch. Does that mean that you are application code is vulnerable to Machine-In-The-Middle? Not necessarily. First of all, https-proxy-agent is a dev dependency used by the Angular CLI, so it is very unlikely that the vulnerable code will end up in your deployed code. Moreover, it means that deep, deep in your angular library, there is a piece of code (that ultimately may not be used at all in your application) that is vulnerable.

To better understand this point, let’s take a look at the different outputs offered by npm audit. At the time of this writing, you can ask NPM to display the results of the audit command in three different formats: JSON, parseable, and tabular. Interestingly, you can get different insights with each output.

If you run npm audit --parseable results are formatted in a way that allows you to use utilities such as grep and awk to parse the output. More importantly, you get a concise list that shows you the commands that you’d run if you want to fix your dependencies. For instance, if we run npm audit --parseable for the same application we get this:

update	tar	high	npm update tar --depth 4	Arbitrary File Overwrite	https://nodesecurity.io/advisories/803	node-sass>node-gyp>tar	N
update	js-yaml	high	npm update js-yaml --depth 5	Code Injection	https://nodesecurity.io/advisories/813	@angular-devkit/build-angular>postcss-loader>postcss-load-config>cosmiconfig>js-yaml	N
update	fstream	high	npm update fstream --depth 5	Arbitrary File Overwrite	https://nodesecurity.io/advisories/886	node-sass>node-gyp>fstream	N
update	set-value	high	npm update set-value --depth 13	Prototype Pollution	https://nodesecurity.io/advisories/1012	@angular-devkit/build-angular>@angular-devkit/architect>@angular-devkit/core>chokidar>anymatch>micromatch>braces>snapdragon>base>cache-base>set-value	N
update	set-value	high	npm update union-value --depth 13	Prototype Pollution	https://nodesecurity.io/advisories/1012	@angular-devkit/build-angular>@angular-devkit/architect>@angular-devkit/core>chokidar>anymatch>micromatch>braces>snapdragon>base>cache-base>union-value>set-value	N
update	mixin-deep	high	npm update mixin-deep --depth 12	Prototype Pollution	https://nodesecurity.io/advisories/1013	@angular-devkit/build-angular>@angular-devkit/architect>@angular-devkit/core>chokidar>anymatch>micromatch>braces>snapdragon>base>mixin-deep	N
update	lodash	high	npm update lodash --depth 8	Prototype Pollution	https://nodesecurity.io/advisories/1065	node-sass>gaze>globule>lodash	N
update	lodash.mergewith	high	npm update lodash.mergewith --depth 3	Prototype Pollution	https://nodesecurity.io/advisories/1071	node-sass>lodash.mergewith	N
update	https-proxy-agent	high	npm update https-proxy-agent --depth 6	Machine-In-The-Middle	https://nodesecurity.io/advisories/1184	@angular/cli>@schematics/update>pacote>make-fetch-happen>https-proxy-agent	N

The above is undoubtedly a much more concise list than what you’d get by running npm audit with no flags. The same output also tells us that https-proxy-agent, is at depth 6, which gives us an idea of how buried the vulnerability is. For angular-cli to be vulnerable, it must use a piece of vulnerable code buried 5 dependencies deep.

You can also get the output formatted in JSON, in which case you get a lot more information, including CWE IDs, useful metadata such as exploitability, external references to learn more about the vulnerability in question, and other data.

But what if you want to summarize npm audit results in one table? At nVisium, we always check the results of npm audit when we get access to source code. To make sense of the results of npm audit, we wrote a small script that you can find here. The script grabs the results of npm audit and outputs them in a markdown table that is easy to read and understand. You just ave to run npm audit --json | python3 ~/path/to/parser.py -i -d and parse the results. In the case above, the output shows the following:

| CVE | Module | Dependency of | Depth |  Title | CVSS 3.0 Score | Info |
| --- | --- | --- | --- | --- | --- | --- |
| N/A | handlebars | karma-coverage-istanbul-reporter | 4 | Prototype Pollution | N/A | https://npmjs.com/advisories/755 |
| N/A | tar | node-sass, @angular-devkit/build-angular | 4 | Arbitrary File Overwrite | N/A | https://npmjs.com/advisories/803 |
| N/A | js-yaml | @angular-devkit/build-angular, karma-coverage-istanbul-reporter, tslint | 5 | Code Injection | N/A | https://npmjs.com/advisories/813 |
| CVE-2019-13173 | fstream | node-sass, @angular-devkit/build-angular | 5 | Arbitrary File Overwrite | ? | https://npmjs.com/advisories/886 |
| CVE-2019-10747 | set-value | @angular-devkit/build-angular, @angular/cli, karma, @angular/compiler-cli | 13 | Prototype Pollution | ? | https://npmjs.com/advisories/1012 |
| CVE-2019-10746 | mixin-deep | @angular-devkit/build-angular, @angular/cli, karma, @angular/compiler-cli | 12 | Prototype Pollution | ? | https://npmjs.com/advisories/1013 |
| CVE-2019-10744 | lodash | node-sass, @angular-devkit/build-angular, karma, karma-coverage-istanbul-reporter, @angular/cli | 8 | Prototype Pollution | ? | https://npmjs.com/advisories/1065 |
| N/A | lodash.mergewith | node-sass, @angular-devkit/build-angular | 3 | Prototype Pollution | N/A | https://npmjs.com/advisories/1071 |
| N/A | handlebars | karma-coverage-istanbul-reporter | 4 | Prototype Pollution | N/A | https://npmjs.com/advisories/1164 |
| N/A | https-proxy-agent | @angular/cli, protractor | 6 | Machine-In-The-Middle | N/A | https://npmjs.com/advisories/1184 |
| N/A | handlebars | karma-coverage-istanbul-reporter | 4 | Denial of Service | N/A | https://npmjs.com/advisories/1300 |

When using a markdown interpreter, the above will look like this:

CVE Module Dependency of Depth Title CVSS 3.0 Score Info
N/A handlebars karma-coverage-istanbul-reporter 4 Prototype Pollution N/A https://npmjs.com/advisories/755
N/A tar node-sass, @angular-devkit/build-angular 4 Arbitrary File Overwrite N/A https://npmjs.com/advisories/803
N/A js-yaml @angular-devkit/build-angular, karma-coverage-istanbul-reporter, tslint 5 Code Injection N/A https://npmjs.com/advisories/813
CVE-2019-13173 fstream node-sass, @angular-devkit/build-angular 5 Arbitrary File Overwrite ? https://npmjs.com/advisories/886
CVE-2019-10747 set-value @angular-devkit/build-angular, @angular/cli, karma, @angular/compiler-cli 13 Prototype Pollution ? https://npmjs.com/advisories/1012
CVE-2019-10746 mixin-deep @angular-devkit/build-angular, @angular/cli, karma, @angular/compiler-cli 12 Prototype Pollution ? https://npmjs.com/advisories/1013
CVE-2019-10744 lodash node-sass, @angular-devkit/build-angular, karma, karma-coverage-istanbul-reporter, @angular/cli 8 Prototype Pollution ? https://npmjs.com/advisories/1065
N/A lodash.mergewith node-sass, @angular-devkit/build-angular 3 Prototype Pollution N/A https://npmjs.com/advisories/1071
N/A handlebars karma-coverage-istanbul-reporter 4 Prototype Pollution N/A https://npmjs.com/advisories/1164
N/A https-proxy-agent @angular/cli, protractor 6 Machine-In-The-Middle N/A https://npmjs.com/advisories/1184
N/A handlebars karma-coverage-istanbul-reporter 4 Denial of Service N/A https://npmjs.com/advisories/1300

Much more manageable, right? The depth column shows you how deep a vulnerable library may in your dependencies. This gives you an idea of what commands you’d want to run to fix a dependency. You can optionally remove the max depth value for each vulnerable dependency by removing the -d flag from the command.

The “Dependency of” column tells you which of dependencies that you installed using npm -i leverages the vulnerable dependency. This makes it easier to understand what needs to be updated, as you could potentially run into issues by updating a library used by a library you mean to use. At the moment, the script does not report the CVSS score, given that NPM does not include that in the JSON output. However, it should help you report to your developers or your team vulnerabilities present in your NPM dependencies in a way that is easier to understand.

Hopefully this post helps you understand what NPM audit does and it gives you a few ideas about communicating remediation efforts to your team.