8 ms·
RFC: Make NPM install scripts opt-in
- niros_valtos 5y agoThis functionality is a must nowadays. To reduce the risk, I would lock the packages to specific versions and upgrade only after two weeks or immediately after a critical vulnerability is fixed.
- SahAssar 5y agoI don't think this would have much of an impact since it would only require the attacker to change the injection point from the install to swapping out the "main" entry in the package json to a compromised file, right? I think the problems of npm and the js world in general is the depth and breadth of the dependency trees and the misunderstanding of transitive dependencies. I've heard devs say that they only have a single dependency when using CRA (which IIRC pulls in 1500ish transitive dependencies). I've heard devs say that a dependency is always better (even if it replaces a single line of code) than your own code. Besides that even simple dependencies seem to be updated even when they were "done". Many devs see a repo that has not had a commit for a few months and consider the project dead instead of done, so there is an incentive to keep updating things that didn't need updating or for dependents to switch to "actively developed" projects for their dependencies. So yes, requiring 2fa would be great, making the install steps not able to run arbitrary code would be great, and most of all requiring repeatable builds from source would be great. I still think the problem at the core would remain, which is that the ecosystem is too hooked on excessive dependency usage and sees newness as a virtue.
- KayL 5y agodefault or not, you still run it... I doubt regular developers will review the whole script before run it. how many Linux users copy&paste the command from random webpages without doubt? It's 100% opt-in. So it won't help at all.
- seniorsassycat 5y agoIt reduces the attack surface an attacker would have to target a package with established reason for running scripts.
- megumax 5y agoThat's not really a solution to the problem because the attacker might change the contents of the package instead of adding `postinstall` or `preinstall` hooks. The more realistic solution would be teams of volunteers that are auditing the packages and check the differences between specific versions of those. This doesn't block all possible infected packages, but most of them, which is better than what we have now. Everything is based on trust so you can't stop this, but maybe prevent it.
- dane-pgp 5y ago> the attacker might change the contents of the package instead of adding `postinstall` or `preinstall` hooks. Ultimately, any code inside an npm package needs to be run by default in the context of a sandbox, such as vm2 or SES. That way a developer would have to opt in to granting permissions for a package to run executable code. https://github.com/patriksimek/vm2 https://github.com/patriksimek/vm2 https://medium.com/agoric/ses-securing-javascript-in-the-real-world-4f309e6b66a6 https://medium.com/agoric/ses-securing-javascript-in-the-rea...
- morelisp 5y agoUltimately, JavaScript needs to change the culture around its dependency packaging.
- theli0nheart 5y agoGood luck with that.
- rndhouse 5y agoHello megumax! I'm working on a solution along the lines that you've suggested: https://github.com/vouch-dev/vouch https://github.com/vouch-dev/vouch Vouch lets users create and share reviews for NPM packages. Project dependencies can then be checked against those reviews. Vouch uses extensions to interface with package ecosystems. Extensions currently exist for NPM, PyPi, and Ansible Galaxy. I'm currently working on a website to index known reviews and publish official reviews. Drop by the Matrix channel if you have any feedback or thoughts to share: #vouch:matrix.org
- dane-pgp 5y agoIt's sad to see people reject this suggestion based on such weak grounds. "It won't stop all attacks" is true, but if it raises the costs of attacks, and protects more people that it harms, then it is worthwhile. Similarly, "It's a backwards incompatible change" is not a sufficient argument, as changes like that are made all the time (of course requiring a major version bump). To address the resistance, I would propose a compromise, namely that install scripts won't be run by default unless the publishing account is secured by 2FA, or the previous version of the package also included an install script. That should greatly reduce the attack surface, and pave the way towards requiring 2FA for all packages with install scripts as a later step.
- Arnavion 5y ago>but if it raises the costs of attacks, and protects more people that it harms, then it is worthwhile. But how does it raise the cost of attacks? I don't see why it would be harder for someone uploading a malicious package to embed said malicious code in the index.js instead of in the install scripts.
- unilynx 5y agoIf I was installing the module only for use in frontend, eg to be bundled by webpack, the code in your index.js won't execute on the machine but inside the browser sandbox. That makes it a lot harder to steal ssh keys.
- Arnavion 5y agoYes, that's valid, though it isn't about increasing the cost of attacking (what my comment was about).
- ollien 5y agoIt raises the cost of attack, sure. That said, just about every developer I know is not going to think about it and is just going to hit "OK" at the first opportunity. The VSCode "this directory isn't trusted" warning comes to mind. I know of no one who actually takes that to heart. Perhaps we should, but few will actually take the time, sadly.
- ljm 5y agoIIRC, yarn disables script invocation when installing a package, and there is a separate mechanism to run them if you need to. I don't remember the full details of how it works, though. Given NPM's increasing viability for large scale supply-chain attacks, there are other things to worry about still. Perhaps those other things are more fundamental to NPM's design and can't be changed overnight. It's still helpful to solve these simpler problems.
- noodlesUK 5y agoI strongly support this. There are of course other ways of compromising peoples computers when running untrusted code, but let’s get the low hanging fruit. I have a very simple vue frontend app that I wrote a few years ago, and it somehow has >4000 dependencies (including dev dependencies). The fact that npm install could run code from all of those (which might not be obvious to a newer dev) is flat out dangerous.
- echelon 5y agoIf we're going to develop our software like this (there are no signs of changing), then we need development environments that are fully hermetic. Production deploys tend to be if you use the right tools. Docker images, cert signing, ACLs, network policies, etc. But we have no equivalent for developer machines. And engineers have access to lots of dangerous things. Docker alone isn't good enough.
- noodlesUK 5y agoI've recently switched from linux to MacOS (now that those shiny new MacBook Pros exist), and due to this, I've started using vscode dev containers along with gitpod as my pretty much exclusive development environments. I did this for convenience rather than for security, but I must say I felt a strong feeling of relief when this morning I saw the advisory, and typed `npm` into my terminal and found that it wasn't even installed on my host OS. I think longer term, we're going to find things like Qubes's VM for everything model becoming more normalised.
- endgame 5y agoNix?
- pxc 5y agoI think they mean 'hermetic' in a more restrictive sense. But you can use Nix to generate Docker images and VMs and stuff, so you could use Nix to produce environments that are sandboxed if you want. And builds are sandboxed, which helps with the OP issue
- danenania 5y agoThis seems like a step in the right direction for sure, but what is the threat model here exactly? When would I be concerned about code in an install script but not in the package itself? What we really need are content security policies for node. I want to define at the top level of my project exactly which file system directories and internet domains can be accessed, then have that enforced by the runtime.
- johannes1234321 5y agothe difference is that reviewing code after install is simpler than before install. For review after install I install and fire up my IDE. For review before install I have to manually download the package and figure out the dependency tree and do that for all packages.
- tolmasky 5y agoThis is a great question, and there are actually a number of different reasons: 1. Package installation often happens under different privileges than actually running your end user app. There is unfortunately a lot of "just use --unsafe-perm to make the install work" advice out there which means a lot of people are installing packages as root even though they're not running as root. Also consider that a lot of npm packages ultimately get run in the browser, so the attack surface there should have very little to do with the files on your computer, but install scripts make this not the case. For the same reason, this means that this might be the only kind of attack that has the ability to run on your build machine, since your build machine may not actually "run" your app. 2. The fact that the code doesn't have to be run to be effective makes it quite difficult to spot, and thus increases its likelihood of sneaking into your dependency chain. For example, GitHub will often straight up just hide large `package-lock.json` file diffs, and no one wants to read through those. So a very innocuous patch version change in an otherwise unrelated bug fix could completely fool the PR reviewer: this is because there's no code "history" that would imply any sort of entryway into attack. All the code looks totally reasonable. By having to explicitly allow the package to use install scripts however, all of a sudden the PR would contain a clear indication that new foreign code that isn't represented anywhere in the commit will be run. This is huge. 3. This also takes advantage of the fact that the vast majority of npm installs aren't done by humans, but by CI and production deploys. Again, by ensuring that the attack is completely "passive", that is to say, as long as you get installed you've succeeded. A lot of times this can be triggered just by issuing a PR with automatic build and CI machines. The correct behavior for something trying to run a new script on your machine when a PR is submitted isn't to infect your CI machine, it should be for the test to fail with "package X tried to use an install script".
- pictur 5y agoI may be thinking silly but wouldn't it be better if these dependency reliability issues were fixed by the platform that released the package? Are there any barriers to publishing a malicious package via npm right now?
- Groxx 5y agoIt boggles my mind that languages and package managers do not support ACLs for libraries. leftpad has no need of install scripts, nor `eval`, reflection, or access to my disk or the network. nor should it be allowed to gain them in the future, at least without a million alarm bells ringing and explicit approval. ACLs would allow establishing "moats" of dramatically-more-difficult-to-attack libraries, and encourage libraries to voluntarily reduce their attackable surface to make them more likely to be approved. Instead we have this.
- pxc 5y agoIs the way Deno security works kinda what you have in mind? Some Linux distros also ship SELinux or AppArmor policies with packages by default, but that seems like a kind of inside-out proposition from what you're describing.
- Groxx 5y agoSELinux does a heck of a lot to improve this on a per-binary / per-user basis, yeah. But I want this in process. Because libraries are in practice very frequently treated as black boxes (like binaries)[1], but without any ability to limit their access like we can for users/binaries/etc. There are some WASM things that do kinda what I want and pull off neat end results, but they're kinda tough to use together, and have obvious perf/debuggability/etc costs. e.g.: https://github.com/dtolnay/watt https://github.com/dtolnay/watt [1] unfortunate in many ways, but largely reasonable IMO (simpler abstractions of things we don't want to think about is the whole point of shared libraries), and I don't believe the industry is capable of reversing course on this either way.
- pxc 5y ago> (simpler abstractions of things we don't want to think about is the whole point of shared libraries), and I don't believe the industry is capable of reversing course on this Far from reversing course, the industry seems to be interested in accelerating on the current one— containerization as a deployment mechanism is about doubling down on the black box-ness of binaries. Paradoxically, this doubling down on ‘the whole point’ of shared libraries is also a destructive innovation with respect to the advantages of shared libraries, because it also means surrendering the fine-grained resource sharing that shared libraries, as packaged traditionally, allow.
- smashah 5y agoPackage maintainers implement install scripts for a reason. It should be opt-out, not opt-in. There should be granualar control over which packages, and which scripts you want to block. E.g in your package.json: `"scriptblock": { "puppeteer" : "*" } ` or ` "scriptblock": { "puppeteer" : ["preinstall","postinstall"...] } ` etc.
- d4mi3n 5y agoSadly security by blacklisting always turns into a game of whack-as-mole. If there’s an unavoidable need for an install script that may be a case to mature the facilities a package manager offers rather than requiring packages to run arbitrary code.
- axlee 5y agoYou have no idea of the packages you are installing. Like litterally, none, unless you use very narrow, pure-js ones. Nobody can grok the dependency tree of the top 50 popular packages. Asking for opt-out is the best way for nothing to happen.
- pxc 5y ago> Package maintainers implement install scripts for a reason. It should be opt-out, not opt-in. yeah, because they're allowed to. The only time that install scripts are sane and reliable is when they are part of a community that polices them and requires them to be minimal (e.g., in Linux distros). That's basically never the case with language-specific package managers
- smashah 5y agoAlmost all npm packages are open and the code available in Github - so if npm package maintainers are not being policed then it's the community's fault for leeching and not looking into the code. I run an install script for my package [0], this install script forces 2 lines in users' .gitignore files to prevent EXTREMELY sensitive data being committed into a public repo - despite me telling everyone in the community to not commit these files on god-knows how many occasions. I do this because I care about my users and I know if I tried some funny business then they would rightly call me out for it - NOT "because I'm allowed to". [0] - https://github.com/open-wa/wa-automate-nodejs/blob/0ba7d685066956949a6543e194d85845f8361649/package.json#L31 https://github.com/open-wa/wa-automate-nodejs/blob/0ba7d6850...
- seniorsassycat 5y agoWhy are install scripts so scary? `install: "malware"` is a few steps closer to running than `cp.exec('malware')` in index.js.
- alerighi 5y agoBecause it's not always the case that the code runs inside your machine. For example a package that runs inside a web browser can't do a lot of damage since it's sandboxed, however the install scripts always run on the target machine. Also the install script may run with higher privileges than the one that the program would have when it's executed. For example there is the case of installing packages globally with root privileges, ideally something that you shouldn't do, but how many people runs `sudo npm i -g something`? In that case the install script runs with root privileges. Of course there are rare cases when they are needed: but that cases should be an opt-in, meaning that the user is prompted to run the script (if running interactively) or the user has to provide a command line option to allow specific packages to run scripts, or add an option in the package.json to whitelist specific packages.
- simion314 5y agoI do not see this proposed that would cleanup a part of the npm repository, Google,MS, Firefox could work and find what are most popular packages and then make a list what should be put in the browser or in node, or maybe a blessed optional extended standard library. For example if we detect that 1 million packages depend on leftpad then add leftpad int eh standard library, if 500K packages depend on isValidUrl,isValidEmail then put this int eh standard library. I am sure Google devs could over engineer this to make it optional and include only what you need.
- Lhiw 5y agoThis is something JavaScript itself should be looking at, why we have no standard library for JS I'll never know.
- qudat 5y agoit takes time to get adoption and all browsers have to implement it. Also https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/padStart https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- simion314 5y ago>it takes time to get adoption and all browsers have to implement it. You can make it a library , name it STL-extras. So instead of having 10 packages with 10 copy of left pad, 12 packages with some copies of isOdd and other ridiculosness you have this 1 STL-extra library that is reviewed by Google and Mozilla.
- cxr 5y agoHow does this make for a solution that meaningfully differs from just declaring that any problem can be taken care of by someone else doing all the work? In this case, the work is certainly doable; that's not the problem. "We'll make sure all the code we ship or consume is audited" works as a strategy against the problem that exists here. "We'll make sure it's audited, but we're not going to pay for any of it" would also "work" so long as you're able to achieve that, but achieving it is what presents difficulties. How does your proposal take those difficulties into account? It sounds a lot like when you tell a child that you don't have enough money for something and they tell you to go get money at the bank.
- tehlike 5y agoMy developer environment is all inside docker, which I assume significantly reduces the attack surface.
- speedgoose 5y agoA side benefit of disabling install scripts is no more ads and "looking for a good job" in the logs.