What’s the worst that could happen after
When you open an app or execute a program from the terminal, that program can do anything that you can do.
In a nutshell: Imagine if your computer were to disappear in front of your eyes and re-appear in front of mine. Still open. Still unlocked. What could I do from this moment on? That is what an unknown program could do.
Table of contents
npm install, you may be downloading and executing hundreds of programs.
Programs from nice people sometimes ask for your permission. This is because a developer choose to do so.
There may also be laws that could punish them if they get caught not doing so.
What about programs of which the authors choose differently? Well, such program could do quite a bit.
- It could access any of your files, modify them, delete them, or upload them. This also applies to the internal files used by other applications.
- It could install other programs in the background.
- It could talk to other devices linked to your home network.
What is at stake
Files you might not be thinking about:
- The cookies in your web browser.
- Desktop applications. Chat history, password managers, todo lists, etc. They all use files to store the text and media you send or receive.
- Digital media. Your photo albums, home videos, and voice memos.
- SSH private keys, GPG key rings, and other crypto files used by developers.
Browsers cookies make it so you’re immediately logged-in when you open a new tab for Gmail, or Twitter. An evil program can copy the browser’s cookies file and share it with the attacker.
They could then read any e-mail you’ve ever received or sent stored there. It could also delete any. (Got a backup?) They can naturally access future e-mails as well. Like the ones you get from “Forgot password” buttons. They could also hide any trace of these (e.g. filter rules).
This affects any website you use. Social network? Access to any post or DM — regardless of privacy setting. Company e-mail, Google Drive? That too.
The evil program may configure itself to always start in the background when you open your laptop. A new friend for life!
It could also add local command-line programs that wrap the popular
ssh commands, to make them do a little extra behind the scenes. Next time you run
sudo <something> to perform an administrator action and enter your password—you may have given away full system access. Deploying some code? Running
ssh cloud.someplace.special might let the attacker tailgate along with you, opening one shell for itself and another for you.
Local web server
These background programs could also affect you in a myriad of other ways. I won’t detail those today, except to mention they can keep a local web server running. Spotify and Zoom have been seen in the news doing questionable things with their local web servers.
Is this an npm problem?
Maybe. Technically these concerns apply to any method of executing unknown code. Running
npm install isn’t very different from pasting a command like
curl url… | bash. They both execute a downloaded program from your terminal. The difference is in user expectation.
Upon seeing the url and the
bash invocation, you have a choice: Trust the publisher (the url), or trust the script (download, review, then decide whether to run). The result is generally predictable and without hidden dependencies.
Other package managers
What about Debian (apt-get) or Homebrew? Like npm, code published there is unknown to most of us and hard to review. But, there is an important difference: Peer-review. These traditional repositories are curated by a central authority. You don’t have to trust the script or original authors of each package, so long as you trust the publishers and their curation process.
The scale has changed the game
What about PyPI or Packagist (Composer)? These are like npm. Anyone can publish anything. There is however a difference in scale. PyPI has 194K projects. Packagist is host to 237K packages with 0.5 billion downloads a month. npm has over 1.3 million packages and 30 billion downloads a month. This makes it a much more popular target.   
There is also a difference in habit: PyPI packages have 7 dependencies on average, with typically 1 indirect dependency. And, I would expect most dependencies there to be from authors the user has trusted before.  Snyk.io published in April that the average npm package has a whopping 86 dependencies, with a 4+ levels of indirect dependencies. 
The ESLint package has 118 npm dependencies . Eleventy, a popular static site generator, requires 555 dependencies (Explore dependency graph). Each one of these may run arbitrary shell commands from the terminal both during the installation process, after later when using the tool.
I get it. Now, what can we do about it?
There isn’t a magic bullet to make everything perfectly safe. But, there are a number of things you can do to reduce risk.
For the past year, I’ve been using disposable Docker containers as a way to reduce the risk of compromise. It has controls for network access, and for which directories can be exposed. Docker isn’t a perfect safety net by any means, but it’s a step in the right direction.
My base image uses Debian and comes with Node.js, npm, and a few other utilities (such as headless browsers, for automated tests). I use a bash script to launch a temporary container, based on that image. It runs as the unprivileged
nobody user, and mounts only the current working directory.
From there, I would run
npm install and such. The only thing it interacts with is the source code and local
node_modules directory for that specific project. It isn’t given access to any other Git repos, desktop apps, browser cookies, or crypto files. And, once that terminal tab is closed, the container is destroyed.
Other options for isolating your environment:
Speed and flexibility: Use
chroot. This takes more work to setup, but provides a faster environment than Docker. In terms of security it is comparable to Docker. Read more systemd-nspan on ArchWiki.
Security and ease of use: Use a virtual machine (e.g. VirtualBox/Vagrant). This is more secure by default and offers a GUI for controlling what to expose. The downside is that VMs are significantly slower.
Finally, you can reduce risk by reducing the number of packages you depend on in your projects (and then shrink-wrap them). Especially development dependencies, as these tend to be explicitly aimed at executing from the CLI.
Question yourself and question others before introducing new dependencies. Perhaps even encourage maintainers of your favourite packages to Reduce the size of their dependency graph!
Update (6 Aug 2020): Check out Worrying about the NPM ecosystem by Sam Bleckley, which takes a more scientific look at the problem.
- I’m harvesting passwords from your site by David Gilbertson.
- An idea for improving npm package permissions by David Gilbertson.
- Detect piped curl on the server-side by Phil of idontplaydarts.com.
- Strange things after installing an NPM package by Vladimir Tikhonov.
- When packages go bad, Jake Archibald (Dec 2018).
- Malicious code on npm, Danny Grander, Snyk.io (Nov 2018).
- Shell vulnerability in iTerm 2, Tom Ritter, Mozilla Security (Oct 2019).
- Deconstructing Spotify’s local server by Carl Byström (Apr 2013).
- Apple removes Zoom’s server, John E Dunn, Naked Security (Jul 2019).
- Zoom 0-day: 4+ Million Webcams, Jonathan Leitschuh (Jul 2019).
- npm Install Hook Scripts, Alex Wendland (Nov 2018).