Open Source From Heaven, Modules From Hell

THANK YOU Liran Tal for contributing this article.

Knock knock. It’s 2019.

The past year has been perhaps the biggest yet for open source with several tech giants doubling down on open source investments.

Can you imagine yourself writing software without relying on an open source component?
Are you going to give up on a helpful library that gets something out of the way for you so you can focus on your domain logic? Even if technically possible, it’s probably not a choice you will consciously make in most occasions.

You set out to find a third-party for your project and find yourself immersed in a plethora of libraries that are laid out for you in a garden of code which we like to call Nifty Poutine Meal, also known as npm.

Scroll.

Scroll.

Scroll.

Next page.

“There it is!”

*opens terminal and types in*

    $ npm install node-sqlite

WAIT!

Don’t hit return.

Let’s take a step back and evaluate what this is going to do by breaking it down step by step.

Understanding The Dangers of Module Installs

The very first thing to notice is that the npm client is running as the user you are logged in with and will assume the same permissions. While seemingly obvious, I am highlighting this because I’ve had colleagues calling me to review something and the first thing I noticed is that they are prefixing their npm commands with sudo, such as:

    $ sudo npm build

They followed this pattern because of a broken setup they had on their Mac and didn’t understand its implications. Regardless of the npm run-script you are calling, whether a build or an install step, there is virtually no reason to use npm as the root user or provide it any higher privileges than your work user.

With this in mind, let’s get back to the install command. However, before we step into it, I’d like to ask you how likely are you to run this command in your shell:

    $ curl https://node-sqlite.somestranger.sh | bash

It is my sincere hope that just the thought of running that command is completely disturbing for you, as it rightfully should. There will have to be a very compelling reason for anyone to justify fetching some stranger’s code off the Internet and piping it to a shell.

The same potentially happens when you run npm install. Modules in the npm ecosystem are allowed to define custom commands that will be executed during the installation step. For example, native modules that are written in C/C++ may require to run a compiler on your host machine so the bytecode is compatible with your CPU architecture.

The npm CLI client defines several life-cycle run-scripts that will be executed in different stages. Some examples are the `preinstall` run-script that will execute any command specified by the module being installed, or the prepublish run-script that will execute when the module is being published. The latter usually happens when a maintainer invokes the npm publish command to release a new version of their module.

Even scarier is the thought that whatever module you are installing off of npm has a very high chance of importing by itself other strangers code as well, and whose to say they are trusted or have been vetted?

One way to mitigate this concern is to advise the npm client not to run any lifecycle scripts. This can be done either by setting a global configuration, a per-project configuration using an .npmrc or by specifying an ad-hoc command argument.

For example:

  $ npm config set ignore-scripts true
  $ echo “ignore-scripts=true” > .npmrc
  $ npm install node-sqlite —ignore-scripts

The implications of this setting are:

  1. Due to disabling install lifecycle scripts, builds for things such as native modules will break.

  2. Any invocation of any run-script will fail.

The first implication may be annoying at times, but the second is going to make life miserable for anyone who interacts with the npm CLI in a regular manner which is probably most developers.

While all of this is a worrying concern, it is not even the entire problem scope.

A module may not have any run-scripts defined for its installation phase, but at the same time nothing stops a malicious user to build such module that when “required” will create a child process and execute a command or anything of similar concern.

Safely installing packages with npq

This is why I set out to create npq.

npq is a drop-in enhancement for npm or yarn, with the purpose of running simple static checks for the packages you are about to install and allow you to weigh in on whether you actually want to continue with the installation process or abort right there because something looks, well, fishy.

Once you install npq, you can easily alias it to npm if that’s your favorite, and npq will just pass-through all commands to npm except for the case when you wish to install packages.

When you try to do that, npq will run several checks for every single package and its version that you specified in the command line and will provide you with the output findings.

If nothing of concern was found it will silently continue to install the packages, and when any of the checks failed it will prompt you in an interactive manner so you can make a conscious decision about continuing with the installation and accepting the risk.

npq is bundled with the following checks, which we refer to as marshalls:

  • Package maturity checks a package creation date, if it’s been more than 22 days since it was created this check will fail. The rationale behind this check is to prevent you from installing a malicious package that is new on the ecosystem.

  • Package downloads count will fail if a package has less than 20 downloads in the last month.

  • Package that has no README content will fail.

  • Package that has any pre or post-installation script will fail for the concern of possibly being malicious.

  • Incase you are using Snyk and are entitled to use the Snyk API npq will test if any of the packages and their versions being installed has a vulnerability associated with them.

Any of these marshalls can be enabled or disabled per your preference for checks to perform, but even when they are all enabled they should be treated as a minor aid in spotting potential malicious packages before you install, and shouldn’t be your last line of defense.

If you like the concept of npq I welcome you to join and participate in the project and suggest new marshalls or help improve existing marshalls by fine-tuning them and helping us create a more secure ecosystem so we can all enjoy open source safely.




The contributors to JavaScript January are passionate engineers, designers and teachers. Emily Freeman is a developer advocate at Kickbox and curates the articles for JavaScript January.