In October 2018 I investigated why
nodemon was emitting a strange deprecation warning, leading to the eventual discovery of the now infamous
event-stream npm package compromise. This post shares a unique perspective of the events that unfolded, along with downloadable forensic evidence that has since been purged from the npm registry.
For context, see:
- npm – “Details about the event-stream incident”
- Snyk – “A Post-Mortem of the Malicious event-stream backdoor”
In the media:
- Ars Technica – “Widely used open source software contained bitcoin-stealing backdoor”
- The Register – “Check your repos... Crypto-coin-stealing code sneaks into fairly popular NPM lib (2m downloads per week)”
Dates and times mentioned or pictured in this post are Australian Eastern Standard Time (AEST).
As the compromised and malicious releases have been unpublished from the npm registry and purged from most mirrors and CDNs, for security research purposes you may download the archive
event-stream-compromise-evidence ├── firstname.lastname@example.org │ ├── LICENCE │ ├── examples │ │ ├── data │ │ ├── map.js │ │ ├── pretty.js │ │ └── split.js │ ├── index.js │ ├── package.json │ ├── readme.markdown │ └── test │ ├── connect.asynct.js │ ├── flatmap.asynct.js │ ├── helper │ │ └── index.js │ ├── merge.asynct.js │ ├── parse.asynct.js │ ├── pause.asynct.js │ ├── pipeline.asynct.js │ ├── readArray.asynct.js │ ├── readable.asynct.js │ ├── replace.asynct.js │ ├── simple-map.asynct.js │ ├── spec.asynct.js │ ├── split.asynct.js │ ├── stringify.js │ └── writeArray.asynct.js ├── email@example.com │ ├── LICENSE.txt │ ├── README.md │ ├── index.js │ ├── index.min.js │ ├── package.json │ ├── perf-test.js │ └── test │ └── data.js └── firstname.lastname@example.org ├── LICENSE.txt ├── README.md ├── index.js ├── index.min.js ├── package.json ├── perf-test.js └── test └── data.js
Chances are you can source these unpublished package versions yourself by Finder searching the
node_modules of a project npm installed in last month or so (but not yet updated) for
email@example.com contains the malicious code and is the most interesting to look at.
I first caught wind something was fishy a month ago (October 29, 2018), when I could't work out why
nodemon was emitting a crypto deprecation warning in Node.js v11 (remy/nodemon#1442):
[DEP0106] DeprecationWarning: crypto.createDecipher is deprecated.
It was not until November 21, 2018 that Ayrton Sparling figured out it was due to an attack:
At which point I put out an urgent tweet:
It got very little attention. The JS community that day was apparently more interested in memes and React hooks shitposts:
It took far too long for the community to mobilize.
I attempted to report the threat to npm the next day, as the
event-stream maintainer didn't seem so inclined (a reliable instinct). At first it was not obvious the right way to go about it; I filed a report at npmjs.com/advisories/report, but after submitting it redirected to a 404 page, I got no confirmation email or anything, and there appeared to be no place to track the status. So I tweeted npm to make sure they got it:
npm didn’t directly reply; they only took down the malicious packages after the story went viral a week later. Their tweet about the incident (November 28, 2018) claimed they were notified “yesterday morning”:
I replied that in reality, npm was notified a week earlier:
40 minutes later npm Marketing Vice President @cowperthwait initiated a direct message conversation, asking about what went wrong. He revealed that npm was not monitoring Twitter mentions over the US Thanksgiving holiday weekend. Aparently no one checked the mentions upon returning to work either.
npm then put out a new tweet, dropping the wording about when they were notified:
nodemon maintainer first responded 22 hours after the threat was detected:
nodemon version without compromised dependencies was publish relatively soon after.
Code on GitHub & npm can differ
It is problematic that package source code on Github, what most people scrutinize, can be totally different to what is published to npm. Hackers are exploiting this to undermine the open source “many eyes” security advantage.
For packages with a repository field, the
npm publish command could accept a commit hash argument and the actual packaging could take place on npm servers, pulling the relevant commit. This will have the nice side-effect of ensuring packages can be built reliably from dev dependencies and scripts in environments other than the original author’s.
Security report gatekeeping
- The reporting form could be more obvious and better tested.
- Reports via alternative channels such as Twitter should not be ignored.
- There is always a delay between the initial community report and an official advisory or response, if there is one.
It would be nice if there was a way to audit your projects for unconfirmed community reports of active security threats. There needs to be a clear distinction in the reporting process between security vulnerabilities, which should not be immediately disclosed to the public, and active threats, which should.
Instead of removing bad versions of compromised or malicious packages from the npm registry as if they never existed, they should be made only installable with a special flag. This would reduce confusion and allow security researchers to do their thing without having to contact each other asking for copies.