NPM Audit === A lot of tedious work

On a project recently I decided to run npm audit and then fix 100% of the issues it revealed. The purpose of this is to make sure that I'm not running code in production with known security vulnerabilities. This idea was sparked by a recent security breach where an NPM module was updated to attempt to steal $13 Million in cryptocurrency. It was kind of a pain but I think it was a worthwhile exercise and I learned a lot about NPM and Docker having gone through it.

Here's what I got on the initial run of npm audit:
`found 239 vulnerabilities (1 moderate, 238 high) in 42756 scanned packages`


....................
┌───────────────┬──────────────────────────────────────────────────────────────┐
│ High          │ Prototype Pollution                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ lodash                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ @private/ui-scaffolding                                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ @private/ui-scaffolding > karma-webpack > lodash                │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/1065                            │
└───────────────┴──────────────────────────────────────────────────────────────┘


┌───────────────┬──────────────────────────────────────────────────────────────┐
│ High          │ Prototype Pollution                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ lodash                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ @private/ui-scaffolding                                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ @private/ui-scaffolding > react2angular > ngcomponent > lodash  │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/1065                            │
└───────────────┴──────────────────────────────────────────────────────────────┘


┌───────────────┬──────────────────────────────────────────────────────────────┐
│ High          │ Prototype Pollution                                          │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Package       │ lodash                                                       │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Dependency of │ @private/ui-scaffolding                                         │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ Path          │ @private/ui-scaffolding > webpack-dev-server >                  │
│               │ http-proxy-middleware > lodash                               │
├───────────────┼──────────────────────────────────────────────────────────────┤
│ More info     │ https://npmjs.com/advisories/1065                            │
└───────────────┴──────────────────────────────────────────────────────────────┘


found 239 vulnerabilities (1 moderate, 238 high) in 42756 scanned packages

Ideally, you could use npm audit --fix to have it auto-fix the issues. Unfortunately, there's a good chance this isn't going to work for you as I don't believe it will update past any MAJOR versions.

The reason this ended up being a lot of work was that it usually wasn't the immediate dependency that had the vulnerability. It was more likely a dependency 2-6 levels deep that was the problem.

Here's an example dependency tree describing that problem.

Your Project Uses> Third-Party-Lib-A@1.0.0 Uses> Third-Party-Lib-B@1.0.0 Uses> Third-Party-Lib-C@1.0.0 Uses> Third-Party-Lib-D@1.0.0

Let's say Project D had a security vulnerability that got patched out in version 2.0.5 but  Third-Party-Lib-C@1.0.0 was pinning it to version 1.0.0. Well,  Third-Party-Lib-C is going to have to update itself to use version Third-Party-Lib-D@2.0.5. But maybe it's on version 4 now. This means that for Third-Party-Lib-B to get rid of it's security vulnerabilities it's going to require a potential refactor to pull in Third-Party-Lib-C@4.0.1. Now just continue that pattern up the chain until you get to your code. So you might have been on Third-Party-Lib-A@1.0.0 but now to get rid of the security vulnerability, you'll have to upgrade to whatever version  Third-Party-Lib-A ended up at to patch out its downstream issues. This may come with a ton of breaking changes as which will require refactoring of your own code's implementation.

How long will it take for all of these 3rd party libraries to get their dependencies updated? LibA can't do anything until B fixes its dependencies, B is blocked by C, and C by D. This seems like a real cluster-f and if I'm not understanding it right, please let me know in the comments.

It gets scarier. What if any one of those dependencies along the way is no longer maintained by anyone? You have to find an alternate library to use that is maintained, or fork a library just to make these security updates, and then convince the upstream dependencies to use your library instead. And now you're its maintainer. 😳

If the project is maintained it may mean that I have to open PRs with those open source projects and fix the issues, which I truly don't have the cycles to do.

Luckily for me, In most cases, I was able to simply upgrade the immediate dependencies, IE Third-Party-Lib-A, to the latest libraries and that resolved most of the downstream issues. I also ended up removing a lot of unused dependencies and replaced dependencies that were depreciated. Security vulnerabilities aside, this was a good exercise to keep our codebase lean.

This would have gone more smoothly except that I manage a micro-services-frontend architecture which meant that I had 30'ish frontends with dependencies to fix. Though most of these libraries share a lot of the same dependencies. Also, we run these frontends in Docker containers and I had a misconception on what layers are cached. So even after fixing some of the dependency issues, and re-running npm-audit, I was getting a lot of false negatives as my changes weren't picked up.

It was a good bit of work but we now have all of our front-end repos vulnerability free! In order to enforce this going forward, we've done two different things:

  1. We added some lines to our build tools which block PRs from being merged. Essentially on each push to the branch, it runs some checks like lint, unit-tests, and now npm-audit . If it detects any issues it will fail the build and notify the developer that action must be taken.
  2. We pin all of our 3rd-party dependencies. This is to prevent random build failures on a Developer's PR when he/she didn't even modify dependencies. Also it gives us the security that we'll never update a dependency to the latest version before the community and NPM team have a chance to catch any potential issues with it. Also, there's no sense in updating working code in production just for the sake of updating. We may as well only update the library when there's a feature or bugfix that was made that we require.