You’ve probably heard of JavaScript.
It was invented as a programming language, for use in web pages, that would run not on your web server but inside every web visitor’s browser.
Unlike traditional program downloads, with a pop-up dialog and an “Are you sure” button, JavaScript is shovelled straight into your browser and, by default, runs automatically without asking.
Website features we take for granted these days – pull-down menus, pop-up forms, animated image transitions, clickable icons, and much more – are all achieved thanks to browser-side JavaScript.
Of course, this means that even though JavaScript is a fully-featured, general-purpose language, it can’t be used for just any old thing while you are browsing.
The power of JavaScript is carefully limited by the browser, to reduce the rather obvious risks of running program code that arrived in a web page from “out there somewhere”.
For example, JavaScript programs in your browser generally can’t reach out into other tabs, can’t start or stop other programs, can’t access files on your hard disk, can’t read the registry, can’t scan the network, can’t sniff around in memory.
But JavaScript became so well-known and widely-loved – as coding languages go, it is clean, expressive and powerful – that it has turned into a server-side programming language, too.
Thanks to a project called Node.js
, JavaScript has now joined popular server-side languages such as PHP, Perl, Python, Ruby and Java (which is unrelated to JavaScript, by the way) as a coding platform for building complex systems.
Unlike browser JavaScript, Node.js
is augmented by add-on toolkits to do just about anything you can think of: manage processes, run servers, read local files and databases, control the network, perform cryptographic calculations, transcode images and videos, recognise faces, you name it.
The careful restrictions imposed on JavaScript inside the browser are unnecessary – indeed, are a hindrance – when you’re writing a full-blown application using Node.js
.
More power means more complexity
All these add-on capabilities come with a price: complexity.
For example, let’s say you want to use Node.js
to program face recognition into your website’s login system, and you decide to use the ready-made library called facenet
, a “deep convolutional network designed by Google, trained to solve face verification, recognition and clustering problem with efficiently at scale.” [Sic. Let’s hope the code has fewer errors per line than that short quote.]
Well, facenet
itself needs a bunch of additional add-ons, namely: @types/ndarray
, argparse
, blessed
, blessed-contrib
, brolog
, canvas
, chinese-whispers
, glob
, mkdirp
, printf
, python-bridge
, rimraf
, tar
, and update-notifier
.
So far, so good, but these dependencies have their own needs: chinese-whispers
, for example, needs jsnetworkx
, knuth-shuffle
and numjs
.
And so it goes: jsnetworkx
needs babel-runtime
, lodash
, through
and tiny-sprintf
; and babel-runtime
in turn needs regenerator-runtime
.
The good news is that when you adopt Node.js
for programming, you also get NPM, the Node Package Manager, which sorts out all these dependencies for you.
Automatically. From all over the internet.
The bad news, of course, is that when you adopt Node.js
for programming, you also get NPM, the Node Package Manager, which sorts out all these dependencies for you.
Automatically. From all over the internet.
You may find you can write a five-line JavaScript program that is elegantly simple, but only if your Node Package Manager drags in tens or even hundreds of thousands of lines of other people’s software.
Automatically. From all over the internet.
And keeps it updated, automatically, from all over the internet.
Awash in other people’s code
Simply put, your graceful five-line JavaScript program, behind the scenes, is a hodge-podge of directories and files awash with other people’s code that you couldn’t easily keep track of yourself even if you wanted to, all of it kept afloat automatically by a package management toolkit that is about as complex as the auto-updating system built into the operating system itself, yet not integrated with it or even built in collaboration with it.
(If the previous paragraph seems rather long and breathless, please treat that as a metaphor.)
Perhaps unsurprisingly, then, a recent update to the Node Package Manager introduced a bug that caused it to interfere with the operating system, by incorrectly changing the file permissions of a raft of important system directories that should have been left well alone.
In other words, you had to give NPM operating system superpowers to keep your Node.js
world in order; while doing so, NPM mis-used these superpowers to throw your operating system into disarray by locking the system itself out of numerous mission-critical files:
I found that a selection of directories in
/
were owned by a non-root user after runningsudo npm
and many binaries in/usr/bin
stopped working as their permissions were changed. People experiencing this bug will likely have to fully reinstall their system due to this update.
What to do?
- Keep backups that make a meaningful rollback easy. NPM has caused reliability disasters before, and given its vaguely anarchical nature, will cause them again.
- Skip the 5.7.0 version of NPM. NPM version 5.7.1 is supposed to fix this bug.
- Don’t autoupdate production or development servers. Prove the latest update in testing first.
- Remember that simple software can be immensely complex. Keep that in mind when making time for testing (see 3).
There’s an adage that’s been applied to many “breakthroughs” in software engineering in the past few decades:
I’ve got a programming problem! I know, I’ll solve it using X. Oh, dear… now I’ve got two problems.
Technologies like Node.js
and NPM are both a blessing (because they let you do complex things quickly), and a curse (because they solve the problem of “quickly”, not the problem of “complex”).
Plan accordingly, because those who cannot remember the past are condemned to repeat it.
Anonymous
Type: “…all of it kept afloat automatically by a pacakage management toolkit…”
pacakage should be package
Paul Ducklin
(Cue chicken joke.)
Fixed, thanks!
Sameer Anand
The was a terrible article.. took you a couple of thousand words to get to the damn point.
Paul Ducklin
944 words, according to WordPress.
By the way, did you know you made a typo in the very first word of your comment?
Bryan
By your comment, you did not require the (2,000-word)* explanation of JavaScript and Node.js. But if that’s the case, the title explains everything–so why’d you bother with the article at all?
Not everyone knows of Node.js (or even why they should care), but that doesn’t mean they aren’t allowed to care about security. Or learn more about all three.
I’ve commented nearly three years after Sameer shared his insight with us. It’s not very likely Sameer will see this. However I’m not posting for him,** but for anyone who (as I did) finds this article in the role of “posterity.”
We posteriors gotta stick together. Sameer can be his own.
Bryan
* Maybe you coulda whipped up some quick JavaScript to count it
** Okay, maybe a little for him.
Nobody_Holme
“for every complex problem there is an answer which is simple, quick, and wrong.”
Anonymous
Why would you run npm as sudo? That sounds like a bad idea.
gsaiyaman
I was thinking the exact same thing. You know there is nvm right? I find the article very sensationalist. Don’t blame the tool if you are not concerned about what you give sudo to. Aren’t you a “passionate security proselytiser”? Maybe you should add some knowledge to what you want to “proselytize”..
Mark Stockley
The article is a timely warning about how much faith we all put into installers and package managers that manage dependencies from sundry software repositories, GitHub repos etc.
In this case the problem hosed the systems of people who didn’t realise the 5.7.0 was not a stable release, and who ran npm with sudo. So, in this case, there were a couple of things that users should have done that would have prevented the problem. There are things the npm team could have done too, to save users from themselves.
More importantly though, I think it’s a useful opportunity to talk about the underlying issues of trust and complexity that affect npm, composer, pypi and a galaxy of similar applications.
Not using sudo would not have saved you from the left-pad debacle or from the malware on pipy. All three issues either exploit, or are exacerbated by, the trust users are putting into very complex systems that make complex and potentially risky things look simple and safe.
Paul Ducklin
Is that the ‘nvm’ that you somewhat ironically acquire with the command:
$ npm install nvm
The one with the following dependencies:
copy-commit, ln-package-test, z-lib-structure-dqIndex, q-model, nevermind, sm.hoist.visualcomponents, streamly, zero.lib, smt-helper, observable-sequence, z-lib-structure-dequeue
Just for fun, sm.hoist.visualcomponents depends on:
bash.origin, canonical-json, colors, escape-regexp-component, express, jquery, jsdom, nvm, optimist, org.pinf.genesis.lib, pm2, q, q-io, request, rework, rework-plugin-url, send
Let’s choose org.pinf.genesis.lib next:
JSONPath, bash.origin, deepmerge, extend, fs-extra, jsonwebtoken, node-forge, org.pinf.lib, pinf-it-program-insight, q, require.async, resolve, waitfor, winston
And winston (my last one, honestly) depends on:
async, colors, cycle, eyes, isstream, stack-trace
To misquote Hugo, who was misquoting someone else (Jay Z if memory serves):
If you’re having Node problems
I feel bad for you, son
You’ve got 99 problems
And Node is merely one.
Mark Stockley
Global installs and updates require root, as I understand it, because npm is installed into directories under /usr/local owned by root.
You can change the installation directory to one that you, or another non-root user, owns.
annoyed_reader
If I wanted a synopsis of how npm works I’d go to wikipedia. I came here to find out about the npm update, there is nothing about the npm update. Thanks for wasting my time and degrading the integrity of Sophos articles.
tylerl
The “update your system by pushing out a new immutable image” philosophy made easy by docker seems a lot more sensible in light of this failure (and the permissions that make it possible).
Never
Wonderful reading
Arie
Thanks for the great content.