Remix.run Logo
dwoldrich 5 days ago

I am an npm user. My reaction to these software supply chain attacks is to stop taking updates unless absolutely necessary for vulnerability mitigation or to selectively take performance or feature upgrades on a package-by-package basis. Obviously, that approach still opens me up to attacks based on when I choose to take updates that coincides with a malicious package release, but I feel like an extreme reluctance to upgrade will mostly keep me safe.

To achieve my goal, would this approach work:

- Pin all of my package.json versions (no prefacing versions with ~ or ^)

- Routine installation of packages both on my local and on CI servers will be done using `npm ci`

- `npm install <package_name> --save-exact/--save-dev` would be used only at the time of adding a package to package.json, followed by an `npm ci`

- Rely on tooling like GitHub Dependabot and CodeQL to inform the team when a dependency should be updated for security reasons and then manually update only the dependency with the desired version using `npm install lodash@4.17.21 --save-exact`, for example

EDIT: Thinking about this more, we would have to forbid deleting the package-lock.json and regenerating it with `npm install` and forbid the use of `npm update` so that package-lock.json would stay stable.

sedatk 5 days ago | parent [-]

It's all good until the day comes that one dependency breaks compatibility and drops support for the version you have, and now you have days of dependency resolution work ahead of you because you've never bothered for years. Usually, incremental and timely upgrades reduce that kind of friction.

stevepike 5 days ago | parent | next [-]

This is very true. It can be a real fire drill if it turns out you need to go up a major version in some other dependency in order to get a security fix. It can get even worse in JS if you're on some abandoned package that's pinned to an old version of some transient dependency which turns out to be vulnerable. Then you're scrambling to migrate to some alternate package with no clear upgrade path.

On the flipside sometimes you get lucky and being on an old version of a package means you don't have the vulnerability in the first place.

libyear is a helpful metric for tracking how much of this debt you might have.

dwoldrich 5 days ago | parent [-]

I have been in the position of having a mix of having to contend with very old (4+ year) transient dependencies brought in by contemporary dependencies where npm and node versions complain about deprecations and associated security issues. I get into icky package.json `overrides` to force these transient dependencies to upgrade.

Sammi 5 days ago | parent | prev | next [-]

My happy middle ground solution that I've been practicing for years is to update all packages every 2 to 4 months.

dwoldrich 5 days ago | parent | prev | next [-]

Could that just be the cost of doing business though? I think of it like when a version of Node goes out of support and you have to upgrade a bunch of stuff and fix build scripts. It's always ends up being days of work upgrading dependencies and re-testing everything.

null_deref 5 days ago | parent | prev | next [-]

In a case of a package breaking compatibility (^) won’t help as well.

paulhodge 5 days ago | parent | prev [-]

solution: add your entire 'node_modules' folder to source control.

dwoldrich 4 days ago | parent [-]

What benefit does doing that give me that the package-lock.json does not already provide?

paulhodge 4 days ago | parent [-]

it's kind of tongue-in-cheek but it would provide the maximum amount of isolation from any upstream package changes. Even if the package versions are removed from NPM (which happens in rare cases), you'd still have a copy.

dwoldrich 3 days ago | parent [-]

James Shore prefers committing packages to source control, so yours isn't an entirely outlandish suggestion. The NPM package removal rug pull you describe is a use case I hadn't thought of.

Rather than loading up my git repo with binaries, I find it more appealing to maintain an enterprise repository that proxies to NPM and keeps a local cache for the enterprise. Part of what bothers me about letting `npm` point to the https://registry.npmjs.org public repository is the valuable trove of information they can gather about what my team is currently working on by watching what we download.

By pointing npm to a hosted repository proxy, not only can we protect against package deletion rug pulls, but we can also keep hidden details about what we are working on right now. There are also uptime benefits from self-hosting a repo, although registry.npmjs.org has been remarkably dependable.

The self-hosted proxying npm repository I have used in mega-corp was Artifactory, and it was pretty great.