HomeBlogStack OverflowGitHub

NPM Audit: The Complete Guide to How It Works - Part 1

29 May, 2020 - 5 min read

Recently, I began working with a research lab to help them with their front-end architecture. In the process of refactoring their build process, I came across this message almost half a dozen times.

found 611 vulnerabilities (600 low, 4 moderate, 7 high)
  run `npm audit fix` to fix them, or `npm audit` for details

That number of vulnerabilities was initially over 15,000 and after running npm audit and npm audit fix a couple of times, I had a sense of accomplishment.

I did it!

But, what exactly did I do?

In this two part article, I dive into exactly that. This article helps you understand how npm audit works, while Part 2 wraps it up with what the --fix flag specifically does. So let's get started.

NPM AUDIT

If you have never heard of the command before, npm audit helps you find (and fix) security vulnerabilities in your project's dependency tree.

To begin with, npm-audit, needs two files to be present - package.json and package-lock.json.

Without those, you'll run into either of the two:

npm ERR! code EAUDITNOPJSON
npm ERR! audit No package.json found: Cannot audit a project without a package.json
OR
npm ERR! code EAUDITNOLOCK
npm ERR! audit Neither npm-shrinkwrap.json nor package-lock.json found: Cannot audit a project without a lockfile

If you don't know how to create a package-lock.json file, you can run this command :

npm i --package-lock-only

So why does it need these two files?

NPM fetches the dependencies and dev dependencies by reading both these files. In the absence of the package-lock.json file, it uses the npm-shrinkwrap.json file.It also uses the shrinkwrap file if both of the files are present. If you want to see exactly how this is done, here is a link to the audit.js file in the NPM repository. The dependencies from the package-json file and the contents of the package-lock file are passed to a function called audit.generate() in the form of parameters called require and sw.

Okay, what next?

In the generate function, a deep clone of the shrinkwrap(or package-lock) file is created. After passing the inputs through a series of functions to scrub and sanitize the data, an object is created containing two keys, namely requires and dependencies. You can checkout the code on how the data is scrubbed here. The require and dependencies keys enumerate the packages required in the project along with their version, integrity(the hash) and more information.

Once it has made this object, npm calls the audit.submitForFullReport(auditReport) function. This makes a POST request to https://registry.npmjs.org/-/npm/v1/security/audits with this JSON object as the body. NPM Audit uses the npm-registry-fetch package for this.

Here is a sample request body of the data sent across:

{
	"name":"package-name",
	"version":"1.0.0",
	"requires":{
		"http-proxy": "1.18.0"
	},
	"dependencies":{
		"http-proxy": {
	      "version": "1.18.0"
		}
	}
}

The response of this API call returns detailed information on any reported warnings and vulnerabilities contained in your repository after checking them with your registry. In most cases, this registry is NPM.

Below, you will see a sample response for the request body from above. We see a sample vulnerability in http-proxy. Each such object contains information about who it was reported by, the relevant dates and more about the finding.

["1486": {
            "findings": [
                {
                    "version": "1.18.0",
                    "paths": [
                        "http-proxy"
                    ]
                }
            ],
            "id": 1486,
            "created": "2020-02-21T14:16:24.023Z",
            "updated": "2020-05-18T14:50:08.944Z",
            "deleted": null,
            "title": "Denial of Service",
            "found_by": {
                "link": "https://twitter.com/_awry",
                "name": "Grant Murphy",
                "email": ""
            },
            "reported_by": {
                "link": "https://twitter.com/_awry",
                "name": "Grant Murphy",
                "email": ""
            },
            "module_name": "http-proxy",
            "cves": [],
            "vulnerable_versions": "<1.18.1",
            "patched_versions": ">=1.18.1",
            "overview": "Versions of `http-proxy` prior to 1.18.1 are vulnerable to Denial of Service. An HTTP request with a long body triggers an `ERR_HTTP_HEADERS_SENT` unhandled exception that crashes the proxy server. This is only possible when the proxy server sets headers in the proxy request using the `proxyReq.setHeader` function.   \n\nFor a proxy server running on `http://localhost:3000`, the following curl request triggers the unhandled exception:  \n```curl -XPOST http://localhost:3000 -d \"$(python -c 'print(\"x\"*1025)')\"```",
            "recommendation": "Upgrade to version 1.18.1 or later",
            "references": "- [Patch PR](https://github.com/http-party/node-http-proxy/pull/1447/files)",
            "access": "public",
            "severity": "high",
            "cwe": "CWE-400",
            "metadata": {
                "module_type": "",
                "exploitability": 4,
                "affected_components": ""
            },
            "url": "https://npmjs.com/advisories/1486"
        }]

Finally, after collating all this information and grouping the issues based on their seriousness, NPM typically calls audit.printFullReport(auditResult) which prints the report as you see it on the screen. To do this, NPM uses a package called the npm audit report.

And that's how you see the outfit that you're familiar with.

┌───────────────┬──────────────────────────────────────────────────────────────┐
│ Low           │ Prototype Pollution                                          │
│ Low           │ Prototype Pollution                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ minimist                                                     │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Patched in    │ >=0.2.1 <1.0.0 || >=1.2.3                                    │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ react-scripts                                                │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ react-scripts > jest > jest-cli > jest-config > babel-jest > │
│               │ @jest/transform > jest-haste-map > fsevents > node-pre-gyp > │
│               │ rc > minimist                                                │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/1179                            │
└───────────────┴──────────────────────────────────────────────────────────────┘

In Part 2 of this article, I dive into how the --fix flag works. Here is a link to it!

Did you like this article? Let me know on Twitter at @SatejSawant. If you spot something incorrect, please write to me at satejs93[at]gmail[dot]com.

I referred to a couple of resources while writing this article. Here they are:

© 2021, Built by Satej Sawant with

Gatsby