Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Vulnerability #319816 – npm fails to restrict the actions of malicious packages (cert.org)
279 points by sebastianmck on March 26, 2016 | hide | past | favorite | 130 comments


  > npm encourages the use of semver, or semantic 
  > versioning. With semver, dependencies are not locked to 
  > a certain version by default. For any dependency of a 
  > package, the dependency author can push a new version of 
  > the package.
I don't see how this has anything to do with semver. Semver doesn't say anything about not locking dependencies to a certain version (i.e., locking to a specific version is totally legal), nor does it have anything to do with allowing package authors to push new versions of their packages (I'm not even sure how to parse this sentence, really... should it be impossible to ever push new versions of a package? (EDIT: maybe it's suggesting there should be a central review process, like the iOS App Store?)).

In fact, the semver spec doesn't even advocate automatically upgrading when new patch versions are released:

"As a responsible developer you will, of course, want to verify that any package upgrades function as advertised. The real world is a messy place; there’s nothing we can do about that but be vigilant."

http://semver.org/#why-use-semantic-versioning


When you `npm install` a package by default when other users `npm install` it it will install the most recent patch version - even if it's different from the one you installed. So if you install dependencies through `npm install --save` which is the default and advertized way - you can get completely different code between production and staging.

As a library maintainer, patches breaking the library is something that happens (not often, but still) - testing can eliminate a lot but not all bugs.


I'm not trying to dispute that a problem exists, only that semver is a red herring here. It seems like the problem that you describe doesn't have to do with semver, rather that it has to do with npm lacking something like lockfiles.


It is semver compounded with the "^x.y.z" version requirements for dependencies that NPM uses as a default when a package author `npm install --save` something.

When someone else installs that package it will bump y or z if `x > 0`, and z if `x == 0 && y > 0` for all dependencies.

You can manually freeze deps to 'x.y.z'.

The main problem is the "^" default.


The main problem is the "^" default.

This is true.

A secondary problem is the culture of having so many dependencies, which has been much discussed this past week. Even if you lock everything to a static version for consistency, how many people really know which packages from which sources they are relying on to build their system today? Presumably you trust your direct dependencies, and they in turn trust theirs, and so on, but all it takes is one package five levels deep in the tree where the developer was in a rush and npm install'd something vulnerable or malicious to compromise the entire tree.


Even if you manually freeze deps to 'x.y.z', the deps of your deps might still be specified with '^'. Ideally you'd shrinkwrap, and commit node_modules to your repo. (In case packages get deleted)


What's the point of shrinkwrap if you're commiting node_modules? And also why not use a package manager like RPM, Debian, etc so you don't bloat your Git history? (Genuine questions, I know many advocate what you're saying).


Vendoring by storing node_modules works great. It's not necessary to shrinkwrap in that case -- all the same data is derived from the node_modules directory tree. For production, I often use a separate git repo for storing node_modules, referenced as a submodule of the code repo. This has two advantages:

* You retain history for your full dependency tree separately from your codebase.

* Every commit of your code repo exactly specifies the contents of its dependency tree, since the submodule references your deps repo by commit hash.


Re-re-posting a comment I've made in a few threads now, because I feel this needs to get more awareness:

I'm still learning and prototyping my first JS/NPM/React project, but https://github.com/JamieMason/shrinkpack seems to me like it solves _most_ of the issues involved here. It pulls down all the tarballs, and updates the npm-shrinkwrap.json to point to those instead. That way you check in a much smaller "node_shrinkwrap" folder of a few hundred tarballs and 15-20MB, rather than a node_modules folder of 30K files and 150MB and a bunch of platform-specific build outputs.

Still doesn't solve the issue of installing new/updated dependencies that might actually require pulling in something that vanished, but at least once you've done an install and run "shrinkpack", you've got everything you need to rebuild right there.


what is the recommended way to globally change that default?


    npm config set save-exact true


> npm lacking something like lockfiles

`npm shrinkwrap --dev` will give you a shrinkwrap file, with dev-deps also locked. Still, it took me 3 months of heavy Node usage before I found that out. Over in PHP land, while Composer has its own set of faults, it's at least generating a lockfile automatically.


> you can get completely different code between production and staging.

That's not NPM's fault, its the fault of the way you deploy code. Even if you locked down a version, git is mutable so someone could change their code. That's why I "rsync" the code to production, so I know its the same as development.


It's also why one wag suggested using Twitter (which "doesn't have an edit button") as a repository:

* https://gist.github.com/rauchg/5b032c2c2166e4e36713


I mean, don't use `npm install --save` then. I'm not really sure why people started using it in the first place, it's such a lazy thing to do. Instead, add it to your package.json yourself with the exact specific version you want (none of the ^a.b.c funny business).


Instead, add it to your package.json yourself with the exact specific version you want

Unfortunately, the same problem then arises for your dependencies. If any of them don't specify exact versions, you are still vulnerable to getting uncontrolled changes.

This is why things like npm shrinkwrap exist, but it's still crazy that NPM's default behaviour is the uncontrolled case.


Yes, libraries should specify exact versions as well, it's insane that they don't.


or rather, npm install --save-exact :)

then upgrade with npm shrinkwrap


echo "save-prefix=''" > ~/.npmrc


When I bootstrap code I npm install --save, I have 10 packages I usually need right off the bat and I don't want to start an investigation every time I do this.


I think that semver encourages unaudited updates by acting as a substitute for auditing in practice. Obviously the spec doesn't say that you should blindly accept all bugfix updates, but in practice many people do. I often do.


Everyone does and I don't think we will be able to change that.

It would be nice if there would be a tool that would allow developers to mark a new release as safe. Every package would have it's social safety score and you could decide if you want to investigate a release further.


What do you mean by 'safe'? There is such a tool built-into semver -- it's releasing with a patch or minor version bump! Which means it should be entirely backwards compatible with the previous release. Do you mean something else by 'safe'?

I think the issue parent is worried about is if you can't trust the author's declaration of safety.


I think davnn meant a voting mechanism to allow other devs (besides the package's publisher) to vouch for its safety.

At least that's how I interpreted "social safety score".


Ah, I see. I guess that might be interesting. I suspect it would have to get very complicated supporting 'chains of trust' and possibly crypto implementation, to avoid gaming by someone who wanted to make their dangerous code look voted "safe" by lots of people.


Exactly!


> "As a responsible developer you will, of course, want to verify that any package upgrades function as advertised. The real world is a messy place; there’s nothing we can do about that but be vigilant."

Being a programmer you may, of course, try to automate that verification process with something like greenkeeper.io. That opens up its own kind of exploit opportunities.

https://twitter.com/davemethvin/status/711181897712455680


It's poorly worded. The real problem is that _by default_ npm doesn't pin you to an exact version of a dependency.


Unless I'm not understanding this correctly, every package manager is vulnerable to this attack (along with many others). I'm not sure why someone bothered to write this down and make an official "disclosure". Maybe someone more knowledgeable can explain?

I mean really the idea is just that if someone got somebody else's password, they could use it to trick other people into installing a program. Even email has this problem. So really the only thing NPM could be accused of here is not doing more to make publishing secure (like using two-factor authentication).


Similar problems exist in most package management systems. registries that have a manual review process mitigate this danger, but there's still always a risk of malicious code getting into the world.

Having said this, we'd like to make exploits such as those discussed in #319816 as difficult as possible. We're exploring supporting new authentication strategies: such as 2-factor authentication, SAML, and asymmetric key based authentication (some of these features are already available in our Enterprise product, but haven't made it to the public registry yet). npm's official response has more details on this subject:

http://blog.npmjs.org/post/141702881055/package-install-scri...


Unfortunately I don't think that many/any of the Programming language package repositories have manual review processes, or even automated checking for things like known malware...

Linux package managers are a different story of course.


Firefox has automated "malware checking" for extensions, the Mozilla AMO Validator, and it's been basically torn to pieces by the community for being not actually secure [1] plus a major hassle for developers [2], to the point that large extensions with hundreds of thousands of users have stopped using the official Firefox extensions repository.

[1] https://bugzilla.mozilla.org/show_bug.cgi?id=1227867#c2

[2] https://forums.zotero.org/discussion/28847/zotero-addon-is-n...


Yep it's a really nasty problem for any package manager that operates at scale.

The problem is that without any centralized validation of packages, it leaves checking to each developer who uses the libraries and obviously from an effort standpoint that just makes it worse (i.e. if it's hard for the repo owner to do validation it's hard x number_of_users for it to be done by end users)


The problem is basically how the centralized validation is supposed to work. For e.g. the Linux kernel, it's doable because all code in the kernel must (almost by definition) interact with some other part of the kernel. Thus someone else than the code owner, being responsible for those other parts of the kernel, can be tasked with signing off on the new code being good and non-malicious.

But for NPM or PyPI, where anyone can upload anything, how's that supposed to work? It's perfectly fine for someone to put a package called "removeallfiles" on PyPI which executes "sudo rm -rf /". This isn't (by itself) malicious code. The same code, but obfuscated and put in the package name "isarray", is perhaps obviously malicious. But what about something in the middle, e.g. some form of practical joke package? What central authority decides what is allowed and what is not on PyPI?

Signing is a tangential issue. As long as you're trusting the dev who uploaded the code, what difference does it make whether they used password or public key auth (effectively)?


Well if there's no central validation, that leaves all individual users to validate packages before use (which is a huge amount of work)...

The problem is that companies are using these packages as though they are trusted (i.e. not validating them when using them), and that's part of the value proposition in the first place (i.e. it's easier to use this package than write it myself), but it's missing the cost of validation.

On signing I'm not sure we're talking about the same thing. I'm referring to developers cryptographically signing packages before pushing to the repository, with a key that the end-user can validate. the idea is to protect against a comrpomise of the repository. There's a good discussion of the risks and potential solutions on The update framework's site (https://theupdateframework.github.io/)


I completely agree on the first two paragraphs.

Wrt. signing: I'm assuming we are talking about PyPI and NPM here. Also I'm assuming the major threat vector for a repository compromise is that (some of) the dev's accounts on some other services (most likely email) are somehow compromised. In which case it's down to that dev's OPSEC practices whether the repository can be compromised using the data from $OTHER_SERVICE. If the dev has poor OPSEC and would reuse the password for multiple accounts in a user/pass repo auth scenario, it's reasonable that this person would also have emailed themself the keys for signing packages, e.g. for transferring to another location behind a firewall. In either case, you're down to trusting the dev's OPSEC.

IMO, the threat models for other kinds of compromises which signing protects against are much more far fetched. AFAICT neither PyPI nor NPM use third-party mirrors, which basically leaves MitM attacks. If an attacker is capable of successfully MitM-ing the connections you make to PyPI/NPM over https, you have much bigger problems.

Or am I missing your point here?


Ah yes, so the threat model for developer signing is compromise of the repository. So here we're looking at the OpSec of the repository owner (e.g. PyPI, npm, Rubygems etc), and also the risk of deliberate compromise by the repo. owner (for example where a state with authority over the repository compels them to modify a package)

In terms of compromise there's already been the attack on Rubygems in 2013, but in general the thought here is that these repositories are extremely tempting targets for well-funded attackers. A compromise of npm for example would give an attacker direct access to a very wide range of targets.

Combine this with the very limited resources of the repository owners (most are free resources, likely constraining the money available for defence) and you get a realistic risk of attack, which is mitigated by an appropriate use of signing by the developer.

Docker hub has deployed an implementation of the Update Framework to address this, although the interesting point now is whether people actually use it as it's not compulsory...


Unfortunately I don't think that many/any of the Programming language package repositories have manual review processes, or even automated checking for things like known malware...

It depends on what kind of repository you're trying to build.

If you're talking about something like NPM, PyPI or CPAN, then sure, these are relatively open systems where anyone can contribute but that includes bad people.

An example from the other end of the spectrum would be Boost for C++, which is heavily curated and peer reviewed, good enough in quality that its libraries sometimes become part of the full C++ standard at a later date, and tiny compared to the others I mentioned before.


I don't think this is unfortunate at all. I shouldn't have to wait for someone to review my code before publishing an important bugfix. This is the primary thing that drove me away from mobile apps.


On the flip-side how is someone who's using a package from one of these repository meant to validate that it's secure and non-malicious?

without central validation, each user would have to do it, and that's frankly impractical...

The alternative is that no-one actually does the validation and runs the risk of insecure or malicious packages. To me, that's totally fine as long as they're doing it knowingly, however I'd suggest that most companies making use of NPM, PyPI, Rubygems etc are not doing it knowingly...


On update, can you send email to the purported author, telling them they've updated the package? (Similar to those "you have logged into some site from a new computer" emails.)

An easy way to undo a publish would also be useful.


Totally about your first suggestion. But just a reminder, this whole look into NPM began because someone deleted his published packages. Don't know if that's something we should be adding. Deleting versions to me sounds like rebasing public git history.


How about a delay before making the package publicly available so that there could be an undo window? I'm not sure what an appropriate duration would be, as there are obviously situations where someone might want to publish an urgent bugfix quickly.

This could also save publishers from their own "whoops" moments, akin to gmail's super-handy "undo send" feature[1].

[1]: https://support.google.com/mail/answer/1284885?hl=en


Looking forward to 2-factor authentication in npm! For what it's worth, I find Google Authenticator offers a user better experience than text message based MFA.


Until you lose your phone. There is no way to back up/recover. So it's tied to this particular device forever. This has been reported years ago and never fixed. Use authy or sms.


I don't think that's accurate. I've changed phones multiple times and the worst I had to do was find my list of 10 recovery passwords to use one to get in and change the phone I use with Authenticator.

Usually I would just login and add the new phone.


Agree, I've lost phones with many of my 2FA tokens on it, I pulled out my backup codes and got back into all 10-15 or so sites which I use 2FA on.

With the exception of a SMS to a registered phone recovery method, if I can loose my 2FA token, and loose my backup codes, and still access my account, then the 2FA implementation is IMO deeply flawed.


On a personal note, I despise apps like Authy. Every site I've tried to use 2FA on, which didn't use the standard HOTP/TOTP/U2F used an entirely different proprietary app. I have zero interest in having multiple 2FA apps, and even less interest in remembering which app I used on which site. While this clearly isn't Authys fault, its nevertheless a problem.

In other words, please just use standards based 2FA - or SMS/Phone calls.


I just save + encrypt the original QR code somewhere at the time of scanning it.


The note offers this workaround for npm: "Use npm shrinkwrap to lock down your dependencies", which will prevent the worm from spreading purely because of an install of a checked out app.

Any application package manager with a lockfile-based-workflow (like Bundler, Cocoapods, Cargo, etc.) would at least have this mitigation as a default part of the workflow.


shrinkwrap might work for a bit. but if you regenerate the file you will run into the same issue.

a way to protect you 100% against the problem is to define your dependency as a link to a specific commit or tarball.


Or a specific version since they can't be written to twice in the npm repo.


According to https://news.ycombinator.com/item?id=11341142 , in at least one case, now, this is not true.


Except the exact same code was republished, so the point still stands.


This time


IMHO the primary issue at play here is that publishing to the npm repository doesn't currently require proof of user presence, which enables a worm to propagate to other packages automatically.

The npm team is working on 2fac (https://twitter.com/seldo/status/713623991349411840) which will be an adequate solution to this issue.


Take Maven as an example: it's not vulnerable to this attack for several reasons:

1) No install scripts. Fetching a dependency in NPM will execute arbitrary code. Fetching a dependency in Maven doesn't execute any code from the dependency. Obviously when I run my project, I'm expecting to call code in that dependency, so this is a mitigation not a complete fix. But that does lead on to the next point.

Corollary: You have to change the code in my project to spread the worm, not just add a new dependency, otherwise your worm code won't get executed. This is probably a bit more tricky to get right.

2a) Code deployed internally from CI servers, not local machines. It's got to be code-reviewed before it gets pushed to my employer's package repository.

2b) Code needs to be signed before being uploaded to Maven Central. I'm not going to start typing my GPG key into random unexpected prompts.

Malicious code is still a possibility, but the scope for a worm is much less.


Unfortunately, it's not as simple as disabling `postinstall` hooks. In dev,, especially, the Node process likely runs as the same user as the one who publishes packages. There is nothing stopping the code from spawning `npm` and publishing a malicious project as soon as it is require()d. And of course, you're requiring it at some point, otherwise why would you install it?

A better fix to this issue is to require publishers to enter a two-factor token, to email them to confirm publishing, or the like.

Yeah, it makes everyone a bit uneasy with how much trust is involved in the ecosystem. Is there a better solution?


Rather than 2FA, Maven requiring a GPG signature provides that extra security for me. Neither are infallible -- malware could infect your system sufficiently to intercept your next legitimate authentication.

Also, disabling install hooks in NPM would make things really difficult for packages that rely on native code as they've traditionally been compiled on install. I consider that an anti-pattern, but it's one that's unlikely to be removed any time soon.


Not every package manager, for example the Solaris 11 package manager explicitly does not support install time scripting for this reason among many others:

https://blogs.oracle.com/sch/entry/pkg_1_a_no_scripting


If I use maven, dependencies are plain JAR files. Adding dependency doesn't do anything but simple file manipulations. To affect build process, maven uses distinct kind of dependency called plugins.

Actually I'm surprised that npm uses some kind of scripts. All I want is to download some JS files. Why is there any scripts at all? I guess it's needed for native compilation, but it's a lazy solution, there could be better solutions.


There are a surprising number of npm packages that provide a wrapper around a native library to expose bindings to node devs. I use node-sass on the dev side and mmmagic on the production side, both of which require the presence of binaries.

I understand the danger inherent in this system, and actually do keep an eye on dependencies I require. All that said, it's certainly a lot easier to have npm install handle fetching and building native libraries than it is to figure out a way to manually get those libraries attached to the node package (wait, did I install that in /opt, /usr/local, etc etc).

Ultimately, I'm downloading code someone else wrote and executing it. Yes, post- and pre-install hooks are low hanging fruit for malicious exploitation, but so is installing any large library, you can just as easily put Bad Code in a library you distribute for any other language and wait for someone to run it. The difference here is that there's an exploit possible at install time, rather than runtime.


I would say Debian is not vulnerable (to step 6), even for users of the rolling "unstable" release, since maintainers need to sign package uploads with their PGP key, which is usually protected by a separate password.


One of the problems is that npm (and others) put their credentials or some form of API token into dotfiles in the developers home directory, meaning that if you can execute code as the user (via social engineering or malware) you can push new packages.

In some cases it's even accepted practice to put the actual username/password in the clear in a dotfile, which means anyone who can even read a file from the users home directory, can gain persistent access to push packages as them...


Well, isn't proper authentication a solution in and of itself? Using keys with pass phrases or requiring sudo to publish would theoretically mitigate this issue.


No, because it can just sit in the background and wait until you type your passphrase at some point. As soon as you run malicious code, it’s all over; no workarounds.

It would be nice if npm didn’t run arbitrary install scripts by default…


I have a feeling that a lot of other systems also provide "the capability for a self-replicating worm", as that's just the nature of computers in general, and part of why they're so very useful.

To me, the fact that this "vulnerability" requires explicit user action, akin to deliberately downloading and running malware, says that it's really a property of all software ecosystems in which people can publish and disseminate freely.

In that respect, it's nice to see a "this is as intended" response instead of the typical direction of coming up with a set of more draconian policies and processes merely to protect users from themselves.

But given what "security research" these days seems to involve, I can almost imagine in the future: "Vulnerability #1048576 - computer allows users to perform potentially malicious actions."


As a developer in the node ecosystem, you run npm install multiple times a day. If one of the dependency you require has been infected, it will look for all the packages you own on npm and will publish a new infected version. Now any time another developer that has one of your packages as dependencies does npm install, it will infect that person again.

Once it reaches a package like left-pad that is used by a ton of libraries, it will instantly infect hundreds of thousands of developers.


Solution:

1) Pin your packages to a specific version. If you aren't doing this already they you are in for a world of hurt when someone who doesn't know what they are doing releases a breaking package change on a minor version number.

2) Shrinkwrap your packages. Once again if you aren't already doing this then you npm install will probably break about once per three months when someone pushes a bad package to NPM.

3) Publish your NPM packages from an NPM in one vagrant development environment and run your code that installs from NPM in another vagrant development environment. If you have one shared environment then you are going to have other issues of which the small chance of an NPM worm is probably going to be the least of your worries.


You forgot (4): either never upgrade (missing out on security and bug fixes) or audit every update to every package which you are pulling down (which in node could be thousands)

I prefer sticking to curated sets of packages with groups of people focused on doing the auditing and security along side my due diligence. I get security updates, bug fixes, far fewer breaking changes, regular updates, reasonable assurance that code works together, and lower risk.

NPM has a lot to learn.


> curated sets of packages with groups of people focused on doing the auditing and security along side my due diligence

How does this differ from how NPM works? For example the set of packages that is utilized by Express is downloaded more than 5 million times per month. There are tons of eyes all over those packages.

Sure if you are installing sketchy packages that have 100 downloads a month you have to do a lot of auditing yourself, but when sticking to the core modules that are used in practically every node project you can benefit from the auditing being done by all the others who use those packages.


I don't think there are eyes all over those packages though.


I feel like, with the left-pad fiasco, the node dev world (and the broader programmer community) is rediscovering the web of trust that makes open source feasible.

I mean, if I distributed a library through some other package manager system, like a .jar file or some code that you install via homebrew, pip, or ./configure.sh && make, I can embed malicious code in the source somewhere. Maybe not all automated package managers are quite as vulnerable to install hooks, but all open source code is vulnerable to trust attacks, at runtime if nowhere else. I ultimately trust the process that gives me nginx enough to let it serve up my code, hoping there's not a backdoor somewhere that is shoving environment variables (and therefore API keys) out the window to a hacker.

You can't assume people are going to review every line of source before they link against a library. You can't assume people aren't going to click that link that looks like a download link on a sourceforge page but is, in fact, a crapware link. People make mistakes all the time.

So, yeah, there's probably room to make npm a little more robust and difficult to specifically target as a vector. But thousands of developers are still going to be writing sass, and using node-sass to build that, which needs to download, compile and execute a binary on the devbox. Making the installation process of libsass take an extra step or two is great and all (and annoying, and probably likely to degrade windows node development most of all, since windows libraries are harder to put in a "standard" place if you're a non-windows dev writing a node library), but people are still going to be running libsass binaries on their local machine without auditing it, trusting that the developers there have good opsec and review everything well.

On the other hand, all this publicity means someone's bound to actually try and build stuff that exploits trust here, either wormlike or just executing an rm -rf in an install hook. So, my trust levels are lowered and my productivity impaired because I'll be auditing more closely all the updates to existing plugins I'm using. Win?


I've been tinkering on one approach to trustworthy OSS ecosystem at https://github.com/chromakode/signet.

The OSS world has grown precipitously in the era of GitHub/npm/etc, and the trust model hasn't caught up. It's not tenable to maintain a GPG keychain for a nested tree of 100 dependencies. Neither is it advisable to keep deferring this problem. We need to come up with a solution for tracking reputation and trustworthy dependencies at this new scale. It's not simply a problem that package repositories like npm can solve for us -- the scope of this problem is human, and an ideal solution will work for both users and developers, and apply to source distributions and multiple package repositories. One of the few silver linings of the events of the last week is that more people are aware of and pondering these issues. I hope we'll see some more discussion and experimentation in this space!


I like the general idea of this and I think it would be interesting to see how various software hubs could integrate it.

An easy thing you could do right now is to put an attestation directory right into your git repo. Then write up your comments (maybe in a file format similar to what you're doing with signet) and do a signed commit into that directory.


Here's a reference work with links to key papers on build system security for anyone trying to improve them:

http://www.dwheeler.com/essays/scm-security.html

Dig into archive.org for Shapiro's OpenCM while you're at it as it had a lot of nice properties. Aegis seemed to as well. Pulling good traits from Wheeler's survey into modern ones would be a good idea. Also, one can re-develop OpenCM, Aegis, etc to have modern features like plugins for common languages/apps or DVCS capabilities.

SCM security techniques date back to 80's-early 90's. No excuse for today's solutions to still lack the basics.


Kind of amusing that this is considered to need a new vuln. report, I kind of assumed it was common knowledge.

Most of the programming language package repositories (e.g. npm, rubygems, PyPi, NuGet) have this kind of installation process and limited/no checks for malicious content.

Also as there's no consistent use of package signing by the developer (it's either unsupported or not very used) there is also a risk of the repository itself being compromised.

I did a talk last year for OWASP AppSecEU that covers this kind of thing. https://www.youtube.com/watch?v=Wn190b4EJWk


A very insightful look at package signing, and why it wouldn't actually improve security for PyPI, by Python packaging guru Donald Stufft:

https://caremad.io/2013/07/packaging-signing-not-holy-grail/


What a great link: topical and well-reasoned! The concluding sentence is interesting: "My biggest hope is that we’ll get a solution where the end user has the relationship with the source of trust and not the package author." If one runs one's own npm registry and audits everything that goes into it, one can have that already with npm.


Yes, that closing remark is very interesting. It would essentially be formalising what we somehow do manually/instinctively today: "Installing numpy/react/etc.? Yes, everyone I know trusts that, so I do too." "Installing random small non-popular package? I better have a bit of a look at the code first."


Indeed package signing is not the holy grail and won't solve all problems, but it is a part of a secure system.

For the problem this blog post talks about, I personally think that keybase is the right solution. You can tie a key to a github repository amongst others and then validate that the package you're installing came from the person who put the code on github in the first place...


Here's the definitive work on the subject from the past:

http://www.dwheeler.com/essays/scm-security.html

He has lots of nice links, too. Hope you can factor some of it into your talks to get it to mainstream audience. I've saved the vid to check it out later. Will be interesting to see an experienced perspective with the modern tooling.

However, I admit I've always thought updating Shapiro's OpenCM or Aegis to distributed style with plugins for modern tooling gets us 90+% percent of the way. Without the problems of 90+% of build and package mgmt systems. ;)


And no intention from NPM to fix (according to the article)


What solution can we propose?


NPM could take a few actions. The original disclosure PDF[1] suggests these:

● Automatically expire login tokens

● Require 2 factor auth for publish operations

● Help users be logged out during install operations

vjeux mentioned a few others on HN a few days back[2]:

● pre-install/post-install scripts should require user to accept or refuse.

● make shrinkwrap by default (and fix all the issues with it) so that running npm install doesn't use different versions when used over time.

● make updating a version an explicit decision via npm upgrade

[1] https://www.kb.cert.org/CERT_WEB/services/vul-notes.nsf/6eac... [2] https://news.ycombinator.com/item?id=11341145

In the meantime, users may want to consider one of the following:

    npm config set ignore-scripts true
    npm logout


It's definitely a nuanced issue.

> ● Automatically expire login tokens

I don't see how this helps the issue at hand; a worm could spread very quickly, requiring just a single publish from each freshly infected user.

> ● Require 2 factor auth for publish operations

This seems very reasonable, and the easiest to implement. It also has the nice effect of being a captcha to the publish operation, which gives it some of the gravitas it deserves in an open ecosystem like npm.

> ● Help users be logged out during install operations

This may break far more packages than might be considered acceptable.

> ● pre-install/post-install scripts should require user to accept or refuse.

Presumably this would be unnecessary with 2FA for each publish operation.

> ● make shrinkwrap by default (and fix all the issues with it) so that running npm install doesn't use different versions when used over time.

Doesn't do much to address the issue at hand. A static dependency tree doesn't mean benevolent dependencies.

> ● make updating a version an explicit decision via npm upgrade

Same issues as shrinkwrap.


> > ● pre-install/post-install scripts should require user to accept or refuse. > > Presumably this would be unnecessary with 2FA for each publish operation.

2FA still doesn't mean you can trust the install script. Not running scripts automatically gives a chance to audit before they run.

And even with 2FA a worm could spread: It could manipulate the local npm installation so whenever you want to upload a package it will modify it during the publishing process giving you a 2nd-factor-request right when you expect it.

The only way to prevent that I can come up quickly is to over a chance to verify the package between signing (which npm doesn't support) and publishing.


While I agree that giving people a chance to disable install scripts in order to audit them before / after running is a good idea, I also think it's somewhat optimistic of you to think that this would actually help.

There's plenty of npm packages that release updates weekly. I may not update them every time (I tend to wait until I see a need), but the rate of update is high enough that most people would just blindly click through after the fiftieth time they installed that package.


>> ● Require 2 factor auth for publish operations

> This seems very reasonable, and the easiest to implement. It also has the nice effect of being a captcha to the publish operation, which gives it some of the gravitas it deserves in an open ecosystem like npm.

I agree, especially since Google Authenticator makes this pretty easy to implement.


"This may break far more packages than might be considered acceptable."

Can you elaborate?


I thought about this a bit more, and I can't think of a single legitimate reason to be logged into npm during a third-party package install. Of course, that doesn't mean packages won't break if anyone was depending on this behavior, but you're right -- perhaps those packages should break.


> ● pre-install/post-install scripts should require user to accept or refuse.

Unfortunately this wouldn't fix the issue. A malicious package could simply require() a JS script that does the same thing upon its first require(). There's no dependency on postinstall hooks for this worm to spread. Presumably, if you're installing packages, you intend to run them.


The article suggests this:

  >
  >As a user who owns modules you should not stay logged into npm. (Easily enough, npm logout and npmlogin)
  >Use npm shrinkwrap to lock down your dependencies
  >Use npminstall someModule --ignore-scripts
  >
I would add to toss a glance at the libraries you import every once in a while. Just to make sure they look sane.


--ignore-scripts won't help much. The act of using any npm module means you implicitly trust all the javascript code in the module and any of its dependencies. Has anyone taken the time to inspect every line of the dozens of modules that many common packages pull in? Not likely.


I've already written it in another comment: a different way of solving the problem would be to build a tool that allows developers to mark releases as safe. (public lgtm) Every package would have a safety score and you could decide yourself if that's good enough for you.


IMO NPM is broken by design, it's too late - time to move on.


"It rather involved being on the other side of this airtight hatchway"

https://blogs.msdn.microsoft.com/oldnewthing/20060508-22/?p=...


This is not the first time I've seen that argument used to justify ignoring persistence attacks.


In development, you should separate your npm publish credentials from your dev execution environment. Use some kind of sandbox where you `npm install` -- a VM is best.

In production, you should review the packages in your dependency tree and ensure that the exact version you reviewed is what you deploy. To that end, you should shrinkwrap your dependencies. Vendoring works well too. Shameless plug: for additional strictness in your shrinkwrap, you can use https://github.com/chromakode/exactly to store content hashes.


Do people do this? It sounds unmanageable, especially if you publish packages depending on other packages.


> 1. Socially engineer a npm module owner...

Social engineering is not accepted in many security bounties. Just saying...


It's a good thing the bad guys don't use social engineering either.


I am always surprised when I hear of developers letting new versions of dependencies go into production. I cannot imagine taking such a chance.

Even if every new version of the total app is tested heavily before production, you lose the inherent stability of shipping the same code that is known stable from the users over time.

Others have said it is important to use new versions of dependencies to get the bug fixes but I don't see that as a good trade-off.


Automatically running pre and post scripts is absolutely insane.


All package managers (that I know of) for dynamic languages offer a mechanism for compiling native code for packages that include bindings to C libraries.

That mechanism could easily be used to achieve the same goal, even if there was no explicit "post-script" mechanism.


Debian solved this particular problem a long time ago, with pbuilder(1): packages that are installed "from source" simply get compiled in a chroot. Strangely, nobody has ever copied the idea.

The modern hipster-language equivalent would probably be to make the package manager depend on the presence of Docker/rkt/systemd, and use it to pull down a dev-env container and build the native bindings in that.


Nix/NixOS - everything is build not only in a chroot, but also in various namespaces. Of course that doesn't help if you actually use a package (directly or indirectly) hence executing it outside of the build chroot.


Similar with Gobolinux, iirc. With a union mount on top to redirect the files written during the install step.

Afterwards the sub-directory in /Programs can basically be turned into an archive for future installs.


the problem with relying on chroot is that (AFAIK) it might not be available on all platforms supported by the language-specific package managers.


The new Docker runs on Microsoft Hyper-V and on Mac's equivalent. So that would actually be a solution.


in a very restricted sense maybe, but for example perl's CPAN also supports stuff like HP-UX, AIX, z/OS, NonStop and OS/400[0].

[0] http://www.cpan.org/ports/


Don't give them ideas!


Yes, with that you don't even need to "socially fool the package owner". You can use common misspelling for famous packages. It gets you very far.

For example "lowdash" instead of lodash.


I wonder if npm has metrics for this - how many times a month are people attempting to "npm install boostrap"?


Once you've installed a malicious package that you intend to use, you've already lost. Even without a pre/post install script, it'll be executed eventually. The underlying problem is managing risk of the packages you install.


This is very true, but execution at install time (sometimes with root privileges) is a bad idea from a security perspective, especially when it happens with dependencies, which makes it very hard to check all packages that you're installing...


Again, they are run only when you run those commands explicitly on the packages in question. The only reason you would do that on code that wasn't yours was is if you were developing on a forked module, which should be trustworthy, or you've at least read it's code.


Unfortunately native modules like canvas cannot be installed with npm install --ignore-scripts


It does seem to me like it would be reasonable enough to not have npm stay logged in after running a command.


Does npm require signing of packages?


nope and not only that it's not even supported AFAIK


Just to share, there is an issue about uglifyjs https://github.com/mishoo/UglifyJS2/issues/936


Ironically the same person who first reported this npm vulnerability used the wrong package name uglifyjs instead of uglify-js in an unrelated github project.

https://github.com/mishoo/UglifyJS2/issues/936#issuecomment-...

https://github.com/samccone/The-cost-of-transpiling-es2015-i...

Or perhaps was it a security experiment to see how long it took someone to notice.


The uglify authors should use 'uglify' per the naming conventions and can easily reserve uglify-js and uglifyjs as empty / legacy packages.


According to the parent link they've been waiting for npm support to respond for a over a month.


They filed an official dispute 11 hours ago.



Now that things like GreenKeeper exists, the ^ should be removed from being a default thing.


Yes, but that should be done in a patch update, so all the semver extremists can ignore the semver violation like they did the first time when "~" was switched to "^": https://github.com/npm/npm/releases/tag/v1.4.3 (note the "3" at the end, instead of "0")


It's disappointing to see you post this Sebastian, sure - Babel was affected by the whole left-pad ordeal but is more drama what would really help right now?

You're a doer - if you want to see something done about it at Facebook no one is stopping you from forking NPM or contributing code to it.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: