"So the only requirement for the victim is to install the library browser-redirect and add it to Express app, like a regular middleware:"
[...]
"I [Article Author] just published the malicious package to npm using npm install browser-redirect@1.0.2. However, in Github you can’t see the malicious code — see Master Branch and release 1.0.2. The reason for this is because npm does not check against Github or any other source control repository."
Security Perspective Takeaways:
1) Downloading and building source code, where authentication is performed between the downloaded source and the exact GitHub branch (rather than downloading a pre-built package or even source code where this branch authentication is not performed) would at least guarantee that the source code could be matched to the source on github, and could be subsequently audited...
2) The broader class of this attack (which includes such things as Windows Update, software auto-updaters, installers and package managers - both Windows and Linux) is basically: download malicious code - inside of a (user) believed non-malicious binary or library...
3) There are some very strong arguments here towards:
a) Always use open source for whatever you can;
b) Always download and compile the source for whatever you can;
c) Always audit source that you've downloaded...
> a) Always use open source for whatever you can; b) Always download and compile the source for whatever you can; c) Always audit source that you've downloaded... I know; easier said than done...
Such useless advice. Your day ends auditing the tool you're going to use to do something else. Security should be done as a collective effort than individually.
npm already has "npm audit fix" to review known vulnerabilities but the point being in the blog is it's too easy to inject malicious code and I hope GitHub (MS) buying npm would make the situation better.
Perhaps if you can easily flag a package as vulnerable from npm command would make people get alerted quickly?
npm audit flag [package name]
And just show how many percent of the downloaded people have flagged it when you try to install it.
You can easily see if it's a false positive by checking GitHub issues by seeing if people are talking about any vulnerabilities.
- Get rid of custom NPM install scripts for dependencies, force modules that rely on native code to bundle WASM or be set up by the user themselves. This closes off a lot of attack vectors. JS dependencies should not be able to execute custom code when they're installed.
- Make NPM treat minor semantic version changes as breaking changes by default. Currently, `npm install` ignores lockfiles for packages with bugfixes.
- In fact, if you want to be a little crazy consider getting rid of versions entirely and using package hashes instead. This has some tradeoffs, but might have some benefits as well.
- Vendor dependencies so you don't need to worry about them changing at the source. Apparently Yarn 2 is planning to do this by default, which is encouraging.
- On a community level, consider making it easier to put user-defined filters on top of NPM that define "safe" versions of specific packages. In Arch we have a distinction between vetted packages and AUR packages. That's not a perfect system either, but it's better than NPM which doesn't really have a ton of obvious indicators whether anyone else has vetted a specific version of a package. Note I'm not saying, "have somebody moderate every single package on NPM". I'm saying, "make little whitelists of safe packages that everyone uses, and let people easily subscribe to those whitelists."
Somebody somewhere has to do the security audit in order for us to have security. The collective part is about, "how do we make sure that the dependency isn't changing behind our back? How do we figure out who has and hasn't done the audit?" The reason why security in the Node ecosystem is so individualistic is because there are so many ways to circumvent collective security audits, and so few ways for users to aggregate those audits and coordinate them.
---
Also, I'll take any opportunity to advocate that we could also solve a lot of these security problems by having a proper permissions system for Node dependencies. This makes it easier to be responsible about what dependencies need what capabilities without necessarily needing to audit every line of code.
//No capabilities, leftPad and its dependencies cannot
//access the filesystem/network/etc...
var leftPad = require('leftPad', {});
//fs-extra can access the filesystem under this location,
//but not other capabilities like network/child-processes/etc...
var fs = require('fs-extra', {
filesystem: './assets'
});
There's been some talk about doing this (see Realms), but there are still a ton of details to work out. And there's always the caveat that doing in-process sandboxing is just really hard.
Deno (Node's 'replacement') is a step in the right direction, but unfortunately only has permissions for the top-level binary. It's not trying to extend that system to dependencies, which I feel like is a kind of big mistake.
I mean, it's probably not actually going to happen, at least not anytime soon, so no.
But ideally, yes, we would head at least a little bit in that direction. SELinux has bad UX, but some of these hard UX problems are solveable with enough effort.
- First, if you're treating Gatsby as a generalized site builder, this is an audit you only need to do once for all of your projects. It's not, "I need to audit 760 modules to build a simple webpage", it's "I need to audit my site builder once before I adopt it for all of my projects".
Of course, NPM makes it harder to treat Gatsby that way, because NPM makes it harder to freeze the entire package and guarantee you're using the same code everywhere. This is honestly true of a lot of package managers, there is a lot of room for future ecosystems to do package management better to help mitigate some of this personal responsibility.
Just as an example, I didn't need to personally do a security audit of every one of Krita's dependencies when I installed it in Arch. I downloaded a version that someone else had vetted. NPM just makes this hard to do.
- Second, the simple webpage part here is a little misleading. Gastby.js isn't simple, it's doing a ton of stuff. So it's not like building a simple webpage requires 760 modules, it's that you're using a site builder that comes with its own web Express web server, that does code transpilation with Babel and Webpack, that includes a code linter.
Here's the list of dependencies for Gatsby.js: https://www.npmjs.com/package/gatsby?activeTab=dependencies. Understanding that this isn't "a very simple webpage" puts that number into perspective and starts to make that security audit look at least a little bit more reasonable.
The Node ecosystem doesn't require 760 modules to build a webpage. It requires 760 modules to build a development environment, and then it bundles that development environment as a dependency. That's a very different situation to be in.
>Should I be doing security audits on every single one of the 760 modules?
Yes, ideally.
I know this isn't practical. The fact that you have so many dependencies and any one of them could do something bad should be a sign that something is deeply wrong in the ecosystem.
The JavaScript ecosystem is famous for bloat like this. Python is much better, though there is still an O(n^m) dependency tree. Languages like C/++ and FORTRAN commonly have effectively O(n) dependency trees.
I suspect it’s related to the ease of use to add new libraries/modules.
I really don't want to get into an argument about this again, but I would add to this list -- if you're already being responsible about your NodeJS dependencies and paying attention to their source, consider going a step farther and vendoring them as well.
If you're building a library that you want to put on NPM, obviously don't vendor those dependencies. But if you're building a website, or a game, or an electron app, there are a lot of scenarios where without too much work you can make sure that all of your dependencies are pure-JS, and then you can just commit them to your Git repo. You get a lot of benefits out of that, but specifically for security you get to know that when you download a repository you're not getting any unvetted code in your dependencies.
For a non-trivial portion of time, vendoring dependencies was just advised as good behavior by the NPM maintainers. And at some point the advice flipped and now everyone in the JS ecosystem hates the idea, and for the life of me I can't figure out why -- as far as I know, languages like Go and Rust still have good support for this, so it's not like it's a fringe practice.
----
"But won't that spike the size of my Git repo?"
Usually no, not unless your dependencies contain large binaries or you have a massive repo. Most of the projects from most of the people reading this don't fall into that category.
"But can't I just set up a private NPM mirror?"
Sure, but you probably won't. And if you do, you'll probably set it up to do something like auto-pull new versions from NPM which defeats the security gains.
"But don't lockfiles solve this problem?"
Virtually none of the engineers at your company care about lock file security. If they get a warning that a hash mismatches, they'll just delete the lockfile and reinstall. Never mind the fact that lockfiles are tied to semantic versioning and allow bug-fix changes unless you run `npm ci` instead of `npm install`. Your engineers are probably not running `npm ci`, and malware can be snuck into bug-fixes.
"But won't this break platform-specific code?"
Yes, but many projects don't need platform specific code. And to be blunt, if your dependencies are pulling in `node-gyp`, you're probably going to end up with an install error some day on an unfamiliar environment that makes you hate yourself anyway. So only use platform-specific code if you really have to. For example, pure-JS implementations of SASS are fast enough that for most projects you do not need the C-compiled version.
Native modules are not such an issue, as they are only one file, and you are probably only targeting 3 different platforms, so you only need to add 3 files to your repo.
There is however an issue with breaking ABI which Node.JS like to do at every major version, eg. two times per year.
I however no longer put node_modules in version control because NPM likes to shuffle around the files in node_modules every time you run a npm command. I instead fork each dependency and link directly to the tarball in the package.json.
But then there are dependencies of dependencies... The only solution is probably to allow users to sign packages and updates. And then you can pick a chain of trust so that if you trust the signatures of a few people they in turn trust the signatures of more people and you probably will end up with popular modules being vetted/signed by many people and thus considered safe.
Well... there's a lot to unpack there, no pun intended.
The two things I'll say:
- First, it is very possible to use React without all of those dependencies, although it does mean skipping `create-react-app`. That could be a longer conversation in and of itself and going that direction comes with its own set of benefits and tradeoffs.
- Second, 307MB for a node_modules folder isn't actually so big that Git can't handle it. People get very frightened about this, but Git's performance on text files is pretty good. Obviously 307MB isn't ideal, but I strongly suspect in most cases you could commit those files in a single `initial commit` at the start of your project, and it would be fine -- you would not notice a major performance difference from Git.
The bigger concern is whether there are dependencies in creat-react-app that require node-gyp. Last time I checked, it wasn't bundling `node-sass` by default, but maybe that's changed. So there's a little bit of digging into your dependencies that you need to worry about to figure out what's going on.
I'm not going to say that `create-react-app` shouldn't be used. But I don't think `create-react-app` absolves you from needing to look at your dependencies and understand them. If you're in a position where you feel like you can't do that, then Git's performance probably shouldn't be your first concern.
yarn 2 will change our workflows where dependencies will be put into zips and checked into git. You will no longer need to install anything. You will only add or remove.
> where dependencies will be put into zips and checked into git.
Nice! I didn't know that, but I'm heavily in favor of that move. Yarn's handling of lockfiles even in version 1 is kind of just in general better than NPM's.
I agree with pretty much all of the recommendations they're giving at that link, particularly around avoiding install scripts.
> Current SASS implementation is made in Dart.
Unfortunately, the majority of Node projects still default to `node-sass` instead of `sass`. But you're correct, the `sass` package is what I recommend people use in most cases.
The problem with Snyk and other automated vulnerability detection tools is the rate of false positives. For my open source library, the false positive rate so far (over many years) has been 100%. Not one of the many reports pointed to a single exploitable vulnerability in my library.
The reason is that a lot of vulnerabilities are context-specific. For example, a common one in JavaScript is Object prototype pollution; this is not an issue at all if the affected function is only used with trusted input.
Just because a function is vulnerable under a certain specific condition, it doesn't mean that it's generally vulnerable.
That's like saying that chainsaws have a critical security vulnerability on the basis that if you put your foot in front of it, you will be badly harmed... and then you use this as justification to warn millions of tree removal experts (and their customers too) all around the world that their businesses are critically vulnerable and fundamentally unsound because they happen to use chainsaws.
The other common problem is when it flags a vulnerability with a specific function within a library, but that function is not actually used by the project. It's not fair to label all downstream projects as vulnerable on the mostly false assumption that the project actually uses the vulnerable part of the code.
I think Snyk does this because it gets them attention and that's how they turn a profit, but they have to get wiser because this strategy is compromising the quality of their service.
Really astounding to see them publish this article today. I have a CVE that's about to go live regarding auditing tools like this one.
I contacted Snyk a week ago to point out that their audit tool (just like npm audit, and others) cannot fundamentally protect you from attacks like this when installed to the same environment as a malicious package. Almost feels like they are trying to get ahead of it.
I was withholding the CVE while other tools are wrapping up their mitigation strategy. NPMJS and Snyk folks basically shrugged their shoulders. This is kind of forcing my hand to publish now.
One can hope that with GitHub supporting signed commits and signed tags that now that they’ve acquired npm there’ll be a way to match up a signed release with an npm release.
Signed releases on npm in general would be a start as then we could at least increase the reliability of security vulnerability notifications.
That being said, as pointed out in another comment, the amount of false positives for instance for regex vulnerabilities in libraries that are only used in the devDependencies of a repository are too high.
Slightly off topic (but considering this is promo article from Snyk):
What is the value of Snyk now that Github and NPM have built-in dependency security audits?
I was a customer of Snyk before auditing was built in to NPM / Github (and before Snyk's price shot up to $750/mo for private repos). Was always confused how they justified that price point now that their main value prop is built in to npm for free.
What if you're halfway down the page but you want to click to another page on the site? How else are you to do that unless the nav menu is on the screen all the time???
Sticky headers are stupid and annoying. Easily the worst UX pattern in the last decade. I bet no one has even tested to see whether they even increase "engagement".
how about something less sinister like not designed for mobile first, and it looks just fine on a desktop with plenty of pixels to be used? like everything else in the world, a decent idea implemented badly makes the idea look bad. also, opinions are like noses. everyone has one and they all smell.
Not sure if you read the article or not but the author shows that this could be an npm package that, if included in a project, would open up a backdoor. The payload isn't large at all and the code base itself is small.
I think you can make a case that Node/NPM is more vulnerable to this than usual due to the huge number of packages pulled in by even simple applications. Can anybody really audit what 10k different packages are doing, and if any of them has had malicious code snuck in at some point?
It's not more vulnerable, it's as-vulnerable as any other package manager where anyone may upload code without sufficient review for people to blindly install and run.
We have already seen this abused in browser extensions being repurposed to inject advertising, in node and ruby modules being converted to malware, cryptocurrency miners being added to games on Steam etc. There was even a Nintendo Switch game that bundled a Ruby IDE, I believe iOS has had apps surreptitiously bundling other software too.
Powershell has a security model. Also, Ryan Dahl (creator of node.js) is working on Deno as a replacement because he thinks he made a mistake with node.
Interesting. You think he is making mistakes again? He complains about "second system syndrome." I've been using Deno for hobby projects and find a lot to like about it.
There's a bit more to it than that. The article touches on the fact that whats on npm doesn't have to match with what's on github (which is true for any package system that isn't 100% backed by source control).
It is also a rather easy attack vector. Publish an npm package with this code and when people pull it into their project they will have opened up a backdoor.
"So the only requirement for the victim is to install the library browser-redirect and add it to Express app, like a regular middleware:"
[...]
"I [Article Author] just published the malicious package to npm using npm install browser-redirect@1.0.2. However, in Github you can’t see the malicious code — see Master Branch and release 1.0.2. The reason for this is because npm does not check against Github or any other source control repository."
Security Perspective Takeaways:
1) Downloading and building source code, where authentication is performed between the downloaded source and the exact GitHub branch (rather than downloading a pre-built package or even source code where this branch authentication is not performed) would at least guarantee that the source code could be matched to the source on github, and could be subsequently audited...
2) The broader class of this attack (which includes such things as Windows Update, software auto-updaters, installers and package managers - both Windows and Linux) is basically: download malicious code - inside of a (user) believed non-malicious binary or library...
3) There are some very strong arguments here towards:
I know; easier said than done...