The new year began with some controversies around the
faker packages on the npm registry, which reminded me that I had a couple of my own scares with npm last year and wanted to write about them.
What is npm?
Talking about npm is a bit difficult, because the term can refer to three different things:
- The Node package manager
- The default package registry, i.e. source for packages
- The command-line application
Those three things are connected in the sense that they are the go-to resource for modern web development, be it for Node.js or the browser. Developers use the
npm CLI to invoke the Node package manager and install external packages and their dependencies for their own projects from a public or package registry, which in most cases is the npm Registry.
What does npm do?
Any project that uses npm to manage its dependencies has a
package.json file, which lists all the primary packages necessary for that project. The CLI tool reads that file to install and manage all the packages and their dependencies, which are typically installed into a local
node_modules directory. Since many packages on the registry define their own dependencies and those dependencies may have their own and so on, this folder can easily become the largest directory in a project, which is why people have come to jokingly rank its mass over that of a black hole.
It also means that a developer may only use npm to install a single external package, but that package may have an infinite amount of dependencies and sub-dependencies that need to be met for it to work, so it will install many more packages the developer may not even know of. Of course it tries to do so intelligently and in doing so makes the developer’s life much easier, but this side-effect may also carry certain risks.
Why is npm good?
Every developer gets taught to keep their code DRY, i.e. not to write the same code multiple times in different places of the same project. Oftentimes, it also makes sense to expand this approach across projects for things that are independent enough and as such can be reused in different contexts.
In this sense, npm keeps developers from having to constantly re-invent the wheel, while also enabling specialised libraries that can be maintained and improved by communities of developers.
Why is npm scary?
Unfortunately, in reality many packages are maintained only by individuals, or very small groups of people, which means they might become unmaintained or be removed at any point in time. This is in itself a potential risk, but it is made worse by the fact that so many packages depend on so many other packages, which in turn depend on other packages. This means that one misbehaving package at the start of the dependency-chain may have massive effects throughout the entire ecosystem, as could be seen when the
faker packages went dark in the beginning of 2022.
On top of that, the npm CLI tool can also execute arbitrary scripts on the developer’s system before or after packages are installed. This can be used for good—but also evil. It has happened multiple times now, that a package somewhere along the dependency-chain got hijacked by a nefarious entity and was modified so it would install malware on the developer’s computer.
It almost happened to me, a couple of months back. I was just updating the dependencies of one of my projects, when the install failed—because of a failing post-install script of a sub-dependency I didn’t even know was part of my project, since it was pulled in by another package I was using.
I got lucky. Since the attacker had made a mistake, the post-install script couldn’t execute properly and threw an error, which showed up in my terminal. Otherwise, I would never have known that a post-install-script had even run! The error message prompted me to check the GitHub-repository of the misbehaving package, where other users had already reported the issue and were discussing the scope of the attack.
Conclusions and Consequences
That incident especially made me worry about the safety of using npm. Had I been attacked before without even noticing it? How could I trust a piece of software that could do basically whatever it wanted without me knowing about it?
The only solution I see, until something different or more secure comes along, is to only use external packages where absolutely necessary, while making sure that those packages have as few external dependencies as possible, ideally none.
I will re-invent the wheel when it comes to something that I can code quickly, like a utility that consists of a single function, but I still rely on packages that would be too complex or time-consuming to implement myself, such as a framework or highly specialised tools for image optimisation, etc.
I often see people installing external dependencies for cosmetic reasons, or tiny little functions they could implement themselves, or use already existing software on their machines for. At the end of the day, every developer has to weigh the risks on their own and make their decisions according to their preferences, but I’d much rather not have fancy colours in my console outputs than rely on yet another external package.
Dependency management with all its constant updates and version changes is stressful enough as it is—I wish I wouldn’t also have to worry about the security and integrity of my development machine on top of that.
Unfortunately, relying on others always comes with a risk. Despite that, I also want to stress how beneficial the hard work of all those good library maintainers out there is for myself and the entire community. Many of my projects wouldn’t be possible without their constant efforts—so in that sense, thank you!
And as always, thank you for reading! If you have any thoughts and / or comments on the subject, feel free to reach out to me via Twitter.