Remix.run Logo
simiones 3 hours ago

Most people have addressed the package registry side of NPM.

But NPM has a much, much bigger problem on the client side, that makes many of these mitigations almost moot. And that is that `npm install` will upgrade every single package you depend on to its latest version that matches your declared dependency, and in JS land almost everyone uses lax dependency declarations.

So, an attacker who simply publishes a new patch version of a package they have gained access to will likely poison a good chunk of all of the users of that package in a relatively short amount of time. Even if the projects using this are careful and use `npm ci` instead of `npm install` for their CI builds, it will still easily get developers to download and run the malicious new version.

Most other ecosystems don't have this unsafe-by-default behavior, so deploying a new malicious version of a previously safe package is not such a major risk as it is in NPM.

lucideer 2 hours ago | parent | next [-]

> in JS land almost everyone uses lax dependency declarations

They do, BUT.

Dependency versioning schemes are much more strictly adhered to within JS land than in other ecosystems. PyPi is a mishmash of PEP 440, SemVer, some packages incorrectly using one in the format of the other, & none of the 3 necessarily adhering to the standard they've chosen. Other ecosystems are even worse.

Also - some ecosystems (PyPi again) are committing far worse offences than lax versioning - versionless dependency declaration. Heavy reliance on requirements.txt without lockfiles where half the time version isn't even specified at all. Astral/Poetry are improving the situation here but things are still bad.

Maven land is full of plugins with automated pom.xml version templating that has effectively the same effect as lax versioning, but without any strict adherence to any kind of standard like semver.

Yes, the situation in JS land isn't great, but there are much worse offenders out there.

simiones 2 hours ago | parent | next [-]

The point is still different. In PyPI, if I put `requests` in my requirements.txt, and I run `pip install -r requirements.txt` every time I do `make build`, I will still only get one version of requests - the latest available the first time I installed it. This severely reduces the attack radius compared to NPM's default, where I would get the latest (patch) version of my dependency every day. And the ecosystem being committed to respecting semver is entirely irrelevant to supply chain security. Malicious actors don't care about semver.

Overall, publishing a new malicious version of a package is a much lesser problem in virtually any ecosystem other than NPM; in NPM, it's almost an automatic remote code execution vulnerability for every NPM dev, and a persistent threat for many NPM packages even without this.

debazel an hour ago | parent [-]

> This severely reduces the attack radius compared to NPM's default, where I would get the latest (patch) version of my dependency every day.

By default npm will create a lock file and give you the exact same version every time unless you manually initiate an upgrade. Additionally you could even remove the package-lock.json and do a new npm install and it still wouldn't upgrade the package if it already exists in your node_modules directory.

Only time this would be true is if you manually bump the version to something that is incompatible, or remove both the package-lock.json and your node_modules folder.

Yeroc an hour ago | parent | prev [-]

> Maven land is full of plugins with automated pom.xml version templating that has effectively the same effect as lax versioning, but without any strict adherence to any kind of standard like semver.

Please elaborate on this. I'm a long-time Java developer and have never once seen something akin to what you're describing here. Maven has support for version ranges but in practice it's very rarely used. I can expect a project to build with the exact same dependencies resolved today and in six months or a year from now.

Tadpole9181 2 hours ago | parent | prev [-]

`npm install` uses a lockfile by default and will not change versions. No, not transitives either. You would have to either manually change `package.json` or call `npm update`.

You'd have to go out of your way to make your project as bad as you're describing.

simiones an hour ago | parent | next [-]

No, this is just wrong. It might indeed use package-lock.json if it matches your node_modules (so that running `npm install` multiple times won't download new versions). But if you're cloning a repo off of GitHub and running npm install for the first time (which a CI setup might do), it will take the latest deps from package.json and update the package-lock.json - at least this is what I've found many responses online claim. The docs for `npm ci` also suggest that it behaves differently from `npm install` in this exact respect:

> In short, the main differences between using npm install and npm ci are:

> The project must have an existing package-lock.json or npm-shrinkwrap.json.

> If dependencies in the package lock do not match those in package.json, npm ci will exit with an error, instead of updating the package lock.

Rockslide an hour ago | parent [-]

Well but the docs you cited don't match what you stated. You can delete node_modules and reinstall, it will never update the package-lock.json, you will always end up with the exact same versions as before. The package-lock updating happens when you change version numbers in the package.json file, but that is very much expected! So no, running npm install will not pull in new versions randomly.

lucideer 2 hours ago | parent | prev [-]

A lot of people use tools like Dependabot which automates updates to the lockfile.