Making Sense Out of NPM Audit
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:
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
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||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||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.