Solving the mystery of the indirect vulnerability


Fixing indirect vulnerabilities is one of those complex, tedious and, frankly, boring tasks that no one really wants to touch. Nobody except developed, it seems. Sure there are many ways to do it manually, but can it be done automatically with minimal risk of breaking changes? The Debricked team decided to find out.

A forest full of fragile trees

So, where do you even start?

First, there has to be a way to fix the vulnerability, which for indirect dependencies is no walk in the park. Second, it has to be done safely, or without breaking anything.

You see, indirect dependencies are introduced deep into the dependency tree and it is very difficult to get the exact version you want. As Debricked’s head of R&D once put it: “You turn the knobs by fiddling with your direct dependencies and praying to Torvalds that the right indirect packages are resolved. If Torvalds is in your favor, sacrifice some cloud storage to Uncle Bob to make sure the updates don’t break your application.”

In other words, there really should be an easier, less stressful way to do it.

In this article, we’ll show you how to fix transitive vulnerabilities manually and toward the end, we’ll show you the Debricked solution, which will help you do it automatically. If you’re really only interested in the solution, I suggest you start scrolling.

Precision surgery on your dependency tree

During the research phase of the graph database projector how Debricked today fixes your open source vulnerabilities at the speed of light, the team ran into some articles explaining how to fix indirect vulnerabilities in NPM.

As mentioned in the article, the `minimist` package is affected by vulnerabilities, namely: CVE-2021-44906 and CVE-2020-7598

These are both “Prototype Pollution” vulnerabilities, which means that arguments are not properly sanitized. Fortunately, the administrators of `minimist` have fixed these vulnerabilities in version 1.2.6.

Unfortunately, `mocha` version 7.1.0 fixes `minimist` 0.0.8, which is within the vulnerable range of these vulnerabilities. As suggested by the author of This articleThese vulnerabilities can be addressed in several ways.

But! What about breaking through change?

The first suggestion is to just trigger an update of all “indirect dependencies”, meaning we won’t actually change the version of `mocha`. To run this update, just run `npm update`, delete your `npm.lock` file and run `npm install`. This will regenerate the dependency tree with the latest possible version (according to constraints) of your indirect dependencies. With this method, the risk of breaking changes is very low because you don’t actually update any of your root dependencies, just your indirect ones.

Breaking changes occur when the package functionality or interface is not forward compatible, meaning an update of the package can cause your application to break. Common breaking changes include class/function removal, changing arguments to a function, or license change (watch out for that one!).

But life isn’t always that easy, and this simple tree update won’t fix the vulnerability. The problem is that `mkdirp` actually locked their version of `minimist` at 0.0.8† This means that `mkdirp` contributors have concluded that they are not compatible with newer versions of `minimist`, and forcing the update of `minimist` may introduce breaking changes between `mkdirp` and `minimist`.

Think… graphs!

So the million dollar question is: which version of “mocha” to use, which in turn trickles down to a safe version of “minimist” without breaking the dependency tree? This is actually a graph problem, which is described in This article

Which graphing algorithm would solve this problem? How NPM resolves dependencies can be a bit complicated, as they are allowed to “split” the dependency tree. This means that they can have multiple versions of a single dependency to ensure we always have a tree that is compatible. To fix the vulnerability, we need to make sure all instances of `minimist` are safe by updating all roots that can trickle into `minimist`.

The algorithm used to solve this problem is called “All Max Paths Safe”. By going down the dependency graph and keeping the max versions, pruning all other versions of that package in each intersection, we can create an approximate representation of our dependency tree. If the approach is safe, it means our real tree will be safe too!

By running this algorithm on all possible versions of `mocha`, we find the smallest upgrade to fix this vulnerability. To get the speed we wanted for this algorithm, the team had to build a custom Neo4j procedure, which can handle searching over 100 root versions with a search depth of 30+ in ~150 milliseconds. Fast, eh?

In this case we don’t have to look far… because 7.1.1 of `mocha` is safe! This is just a patch update, indicating that the risk of breaking changes is very low. For less complex cases (like this example), ‘npm audit’ can help you with their fantastic ‘npm audit fix’ command.

Don’t be ad hoc, enter the pub sub human way of working!

Now, if you’ve come this far (congratulations, very impressive) and thought “this sounds very complicated and like an awful lot of work”, don’t worry – you’re not alone. Fortunately, this all happens completely automatically in the Debricked tool when you click this little button:

This is now available for Javascript. Support will soon be extended to Java, Golang, C#, Python and PHP.

If you don’t have a developed user, what are you waiting for? It’s free for individual developers, smaller teams, and open source projects (and if you’re a larger organization, fear not. There’s a generous free trial). Sign up for free here