Securing the npm software supply chain
Published
The npm ecosystem is really nuts. Anyone can come along and just publish any old thing. We all start using the thing, and it gets downloaded tens of millions of times a week. Trillion dollar companies come to rely on the thing. And then the thing turns into a crypto wallet credential stealer.
The folks at npm have had enough, and they recently published their plan for a more secure npm supply chain.
What is a software supply chain?
You can think of a software supply chain like its real world manufacturing equivalent - a complex set of interdependent "raw materials" that get assembled into a software artifact (like an application binary that gets run on a computer).
Most software created today is composed of a bunch of different open-source software atoms. These atomic pieces of functionality (modules, libraries, and frameworks) get stitched together by developers in ways unique and banal, useful and not-so-much.
A lot of modern software involves trusting a nightmarish pyramid of dependencies. Web development in particular has grown into a field where no human can understand every aspect of the software, and where reproducible builds are almost impossible because the dependencies are all updated constantly. But hey - this stuff is free, right? What are we going to do, pay for it? Write our own?
Implicit in this arrangement is the idea that we can "trust" all of the different dependencies in our projects.
What is a supply chain attack?
In a supply chain attack, you (the hacker) try to compromise a package or environment that is used (or will be used) by other software packages. Some of the more common supply chain attacks these days look like:
- Add a backdoor so that you or your nation state friends can come and visit a system at will
- Steals secrets (passwords, tokens) and exfiltrate them
- Live on a compromised end user's machine and quietly replace crypto wallet addresses on their clipboard with your own address so that they accidentally send you money (seriously)
Why is npm changing?
The JavaScript ecosystem is particularly egregious for its liberal use of third party dependencies. Write a function to figure out if a value is a string? Ain't nobody got time for that. The is-string package gets more than 46 million downloads a week from npm, as of this writing.
I can hear you thinking, "But surely, the language's standard library must include basic functionality like that?"
Lol, my friend. Lmao, in fact.
The npm repository is the largest source of open-source JavaScript packages for developers. JavaScript packages published on npm contain a package.json file that lists its direct dependencies. Direct dependencies are the ones that make it into either the dependencies or devDependencies sections of the project's package.json file. If the person who published your package is kind, they will also have a way for you to figure out the transitive dependencies involved at the time the package was originally built. Usually they do this by publishing the package-lock.json file to a code repository you can go read. Transitive dependencies are the dependencies that your dependencies need. There are a lot of these!
I can't find good aggregate public stats on how many dependencies the average JavaScript package on npm has, unfortunately. From crunching the numbers on my own projects, the average number of direct dependencies is 21. The average number of transitive dependencies in my projects is 354.
The situation is extra dire if I look at just my front-end projects, which are mostly React with some Next.js and lots of prototypes with Vite. In those apps, my average number of direct dependencies is 36 and my average number of transitive dependencies is 862. That's not a typo, either. I'm using an average of 862 software packages to make artless web forms!
How many of those packages do you think I even know about? Of those, how many do you think I've audited by reading the source code?
What is npm changing?
The npm repository is clamping down on how new versions of packages get published. Developers just can't keep getting owned, and when they do, their npm token can be stolen and used to publish new versions of existing packages. To batten down the hatches, they are making three big changes:
- Local publishing (the act of publishing a package on your machine for testing purposes) will require two-factor authentication (2FA).
- Granular tokens which will have a limited lifetime of seven days. Previously, generalized tokens were long-lived.
- Trusted publishing which uses OIDC identity tokens for CI/CD systems to securely publish new versions. Consider this as an alternative to a developer building the thing on their local machine and publishing it manually.
The major changes are detailed below, with npm's words in bold and my commentary in regular-old.
- Deprecate legacy classic tokens. Taking away the ability to make new long-lived, general tokens forces developers to use new short-lived, granular tokens. This also makes sure existing tokens need to be rotated in case they're already owned.
- Deprecate time-based one-time password (TOTP) 2FA, migrating users to FIDO-based 2FA. FIDO-based 2FA is preferable because users don't need to enter codes manually (eliminating a phishing vector).
- Limit granular tokens with publishing permissions to a shorter expiration. Arguably, shorter token lifetime means less chance for a token to be owned and abused.
- Set publishing access to disallow tokens by default, encouraging usage of trusted publishers or 2FA enforced local publishing. We love a safe default, don't we folks? We love a golden path! This path is very golden - everyone is saying they love this golden path.
- Remove the option to bypass 2FA for local package publishing. Another good idea, force good security hygiene in local development too, not just when publishing for real.
- Expand eligible providers for trusted publishing. Notable because npm is owned by GitHub which is owned by Microsoft. We hate a mono-culture!
My guess is that you are going to see folks putting a lot more time and effort into their publishing automations as a result of these changes. It will be seen as uncouth and dirty to have developers manually publishing new versions, I think.
The npm repository already has a visual green checkmark to indicate package provenance, and I think this will become shorthand for a slightly more trustworthy package.
When do the changes come into effect?
Classic tokens can no longer be created, and existing classic tokens will be revoked on November 19, 2025. See this GitHub blog for details on migrating: Strengthening npm security: Important changes to authentication and token management.
Happy hardening!