Npm to Implement Staged Publishing After Turbulent Shift Off Classic Tokens
Key topics
The npm ecosystem is abuzz with the introduction of staged publishing, sparking a lively debate about the implications of "Trusted Publishing" and its potential to concentrate power among a few large CI providers. Commenters are weighing in on the trade-offs, with some noting that while Trusted Publishing enhances security, it may inadvertently create barriers for smaller or self-hosted projects. A consensus emerges that the npm and PyPi ecosystems are walking different paths, with PyPi taking a more cautious approach to adopting similar features, and some commenters wondering if Rust and Python will eventually face the same issues. As the discussion unfolds, it's clear that the stakes are high, with the potential for a shift towards dependency on closed publishers and the concentration of code in the hands of a few giants.
Snapshot generated from the HN discussion
Discussion Activity
Very active discussionFirst comment
34m
Peak period
24
18-21h
Avg / period
9
Based on 117 loaded comments
Key moments
- 01Story posted
Jan 7, 2026 at 1:31 PM EST
2d ago
Step 01 - 02First comment
Jan 7, 2026 at 2:05 PM EST
34m after posting
Step 02 - 03Peak activity
24 comments in 18-21h
Hottest window of the conversation
Step 03 - 04Latest activity
Jan 9, 2026 at 4:04 AM EST
13h ago
Step 04
Generating AI Summary...
Analyzing up to 500 comments to identify key contributors and discussion patterns
Want the full context?
Jump to the original sources
Read the primary article or dive into the live Hacker News thread when you're ready.
This isn't strictly accurate: when we designed Trusted Publishing for PyPI, we designed it to be generic across OIDC IdPs (typically CI providers), and explicitly included an accommodation for creating new projects via Trusted Publishing (we called it "pending" publishers[1]). The latter is something that not all subsequent adopters of the Trusted Publishing technique have adopted, which is IMO both unfortunate and understandable (since it's a complication over the data model/assumptions around package existence).
I think a lot of the pains here are self-inflicted on GitHub's part here: deciding to remove normal API credentials entirely strikes me as extremely aggressive, and is completely unrelated to implementing Trusted Publishing. Combining the two together in the same campaign has made things unnecessarily confusing for users and integrators, it seems.
[1]: https://docs.pypi.org/trusted-publishers/creating-a-project-...
If that is correct, I thought this was discussed when Trusted Publishing was proposed for Rust that it was not meant to replace local publishing, only harden CI publishing.
Yes, that's right, and that's how it was implemented for both Rust and Python. NPM seems to have decided to do their own thing here.
(More precisely, I think NPM still allows local publishing with an API token, they just won't grant long-lived ones anymore.)
It was a good intention, but the ramifications of it I don't think are great.
as always, the road to hell is paved with good intentions
the term "Trusted Publishing" implies everyone else is untrusted
quite why anyone would think Microsoft is considered trustworthy, or competent at operating critical systems, I don't know
https://firewalltimes.com/microsoft-data-breach-timeline/
No, it just means that you're explicitly trusting a specific party to publish for you. This is exactly the same as you'd normally do implicitly by handing a CI/CD system a long-lived API token, except without the long-lived API token.
(The technique also has nothing to do with Microsoft, and everything to do with the fact that GitHub Actions is the de facto majority user demographic that needs targeting whenever doing anything for large OSS ecosystems. If GitHub Actions was owned by McDonalds instead, nothing would be any different.)
There is more than one way to interpret the term "trusted". The average dev will probably take away different implications than someone with your expertise and context.
I don't believe this double meaning is an unfortunate coincidence but a clever marketing move.
In the same category: "Trusted Computing", "Zero trust" and "Passkeys are phishing-resistant"
I can tell you with absolute certainty that it really is just unfortunate. We just couldn’t come up with a better short name for it at the time; it was going to be either “Trusted Publishing” or “OIDC publishing,” and we determined that the latter would be too confusing to people who don’t know (and don’t care to know) what OIDC is.
There’s nothing nefarious about it, just the assumption that people would understand “trusted” to mean “you’re putting trust in this,” not “you have to use $vendor.” Clearly that assumption was not well founded.
(There’s an overlaid thing called “attestations” on PyPI, which is a form of signing. But Trusted Publishing itself isn’t signing.)
I'm certainly not meaning to imply that you are in on some conspiracy or anything - you were already in here clarifying things and setting the record straight in a helpful way. I think you are not representative of industry here (in the best way).
Evangelists are certainly latching on to the ambiguity and using it as an opportunity. Try to pretend you are a caveman dev or pointy-hair and read the first screenful of this. What did you learn?
https://github.blog/changelog/2025-07-31-npm-trusted-publish...
https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-pu...
https://www.techradar.com/pro/security/github-is-finally-tig...
These were the top three results I got when I searched online for "github trusted publishing" (without quotes like a normal person would).
Stepping back, could it be that some stakeholders have a different agenda than you do and are actually quite happy about confusion?
I have sympathy for that naming things is hard. This is Trusted Computing in repeat but marketed to a generation of laymen that don't have that context.
The other difference is being subjected to a whitelisting approach. That wasn't previously the case.
It's frustrating that seemingly every time better authentication schemes get introduced they come with functionality for client attestation baked in. All we ever really needed was a standardized way to limit the scope of a given credential coupled with a standardized challenge format to prove possession of a private key.
You are not being subjected to one. Again: you can always use an API token with PyPI, even on a CI/CD platform that PyPI knows how to do Trusted Publishing against. It's purely optional.
> All we ever really needed was a standardized way to limit the scope of a given credential coupled with a standardized challenge format to prove possession of a private key.
That is what OIDC is. Well, not for a private key, but for a set of claims that constitute a machine identity, which the relying party can then do whatever it wants with.
But standards and interoperability don't mean that any given service will just choose to federate with every other service out there. Federation always has up-front and long-term costs that need to be balanced with actual delivered impact/value; for a single user on their own server, the actual value of OIDC federation versus an API token is nil.
> Federation always has up-front and long-term costs
Not particularly? For example there's no particular cost if I accept email from outlook today but reverse that decision and ban it tomorrow. I don't immediately see a technical reason to avoid a default accept policy here.
> for a single user on their own server, the actual value of OIDC federation versus an API token is nil.
The value is that you can do away with long lived tokens that are prone to theft. You can MFA with your (self hosted) OIDC service and things should be that much more secure. Of course your (single user) OIDC service could get pwned but that's no different than any other account compromise.
I guess there's some nonzero risk that a bunch of users all decide to use the same insecure OIDC service. But you might as well worry that a bunch of them all decide to use an insecure password manager.
> Well, not for a private key, but for a set of claims that constitute a machine identity
What's the difference between "set of claims" and "private key" here?
That last paragraph in GP was more a tangential rant than directly on topic BTW. I realize that OIDC makes sense here. The issue is that as an end user I have more flexibility and ease of use with my SSH keys than I do with something like a self hosted OIDC service. I can store my SSH keys on a hardware token, or store them on my computer blinded so that I need a hardware token or TPM to unlock them, or lots of other options. The service I'm connecting to doesn't need to know anything about my workflow. Whereas self hosting something like OIDC managing and securing the service becomes an entire thing on top of which many services arbitrarily dictate "thou shalt not self host".
It's a general trend that as new authentication schemes have been introduced they have generally included undesirable features from the perspective of user freedom. Adding insult to injury those unnecessary features tend to increase the complexity of the specification.
Accepting email isn't really the same thing. I've linked some resources elsewhere in this thread that explain why OIDC federation isn't trivial in the context of machine identities.
> The value is that you can do away with long lived tokens that are prone to theft. You can MFA with your (self hosted) OIDC service and things should be that much more secure. Of course your (single user) OIDC service could get pwned but that's no different than any other account compromise.
You can already do this by self-attenuating your PyPI API token, since it's a Macaroon. We designed PyPI's API tokens with exactly this in mind.
(This isn't documented particularly well, since nobody has clearly articulated a threat model in which a single user runs their own entire attenuation service only to restrict a single or small subset of credentials that they already have access to. But you could do it, I guess.)
> What's the difference between "set of claims" and "private key" here?
A private key is a cryptographic object; a "set of claims" is (very literally) a JSON object that was signed over as the payload of a JWT. You can't sign (or encrypt, or whatever) with a set of claims naively; it's just data.
I think this is unfortunately true, but it's also a tale as old as time. I think PyPI did a good job of documenting why you shouldn't treat attestations as evidence of security modulo independent trust in an identity[1], but the temptation to verify a signature and call it a day is great for a lot of people.
Still, I don't know what a better solution is -- I think there's general agreement that packaging ecosystems should have some cryptographically sound way for responsible parties to correlate identities to their packages, and that previous techniques don't have a great track record.
(Something that's noteworthy is that PyPI's implementation of attestations uses CI/CD identities because it's easy, but that's not a fundamental limitation: it could also allow email identities with a bit more work. I'd love to see more experimentation in that direction, given that it lifts the dependency on CI/CD platforms.)
[1]: https://docs.pypi.org/attestations/security-model/
> In the interest of making the best use of PyPI's finite resources, we only plan to support platforms that have a reasonable level of usage among PyPI users for publishing. Additionally, we have high standards for overall reliability and security in the operation of a supported Identity Provider: in practice, this means that a home-grown or personal use IdP will not be eligible.
How long until everyone is forced to launder their artifacts using Microsoft (TM) GitHub (R) to be "trusted"?
[1] https://docs.pypi.org/trusted-publishers/internals/#how-do-i...
If you think your setup meets those standards, you'll need to use Microsoft (TM) GitHub (R) to contact them.
Back when I started with PyPI, manual upload through the web interface was the only possibility. Have they gotten rid of that?
My understanding is that "trusted publishing"[0] was meant as an additional alternative to that sort of manual processing. It was never decentralized. As I recall, the initial version only supported GitHub and (I think) GitLab.
[0] I do not trust Microsoft as an intermediary to my software distribution. I don't use Microsoft products or services, including GitHub.
Yes, this makes contacting PyPI support via GitHub impossible for me. That is one of the reasons I stopped using PyPI and instead distribute my wheels from my own web site.
That’s why API tokens are still supported as a first-class authentication mechanism: Trusted Publishing is simply not a good fit in all possible scenarios.
Why not leave decision on what providers to trust to users, instead of having a centrally managed global allowlist at the registry? Why should he registry admin be the one to decide who is fit to publish for each and all packages?
They seem to manage to handle account signups with email addresss from unknown domain names just as fine as for hotmail.com and gmail.com. I don't see how this is any different.
The whole point of standards like OIDC (and supposedly TP) is that there is no need for provider-specific implemenations or custom auth flows as long as you follow the spec and protocol. It's just some fields that can be put in a settings UI configurable by the user.
(But also: for completely unrelated reasons, PyPI can and will ban email domains that it believes are sources of abuse.)
We do leave it to users: you can always use an API token to publish to PyPI from your own developer machine (or server), and downstreams are always responsible for trusting their dependencies regardless of how they’re published.
The reason Trusted Publishing is limited at the registry level is because it takes time and effort (from mostly volunteers) to configure and maintain for each federated service, and the actual benefit of it rounds down to zero when a given service has only one user.
> Why should he registry admin be the one to decide who is fit to publish for each and all packages?
Per above, the registry admin doesn’t make a fitness decision. Trusted Publishing is an optional mechanism.
(However, this isn’t to say that the registry doesn’t reserve this right. They do, to prevent spamming and other abuse of the service.)
It is spelled 'Microsoft'.
What did you think would happen long term? I remember when that acquisition happened and there were parties thrown all around, MS finally 'got' open source.
And never mind feeding all of the GitHub contents to their AI.
on the other, the feature creep is is interfering with their ability to gather all the world's code
if you got this far thinking, what does this have to do trusted publishing? everything has to do with, the trade-offs between getting more of the world's code and making money.
there's this other project - i think you know a lot about it haha - which is going through exactly this issue! uv gets all this valuable telemetry about the world's code, but it also has to make money.
unless someone is going and reading all of the code that gets pushed, trusted publishing is something that prevents an attack but does not prevent all or even most attacks. if you want to fix the problem that trusted publishing is fixing, you have to figure out how to make money from having all the world's code or telemetry about the world's code, rather than from features. Valve and Google have figured this out for their respective media. it's a matter of figuring it out for code.
Great to see PyPi taking a more reasonable path.
Isn't it the case that it didn't ship the backdoor? Precisely because of the thorough testing and vetting process?
It was caught by a Postgres developer who noticed strange performance on their Debian Sid system, not by anyone involved with the distro packaging process.
Sounds like an improvement - having beta builds for people to catch those before they arrive in a stable GNU distribution seems the ideal workflow at glance.
Distro packaging is not perfect, but it is much, much better.
It's OS, it's a collab endeavour
Rust's Cargo is sublime. System apt / yum / pacman / brew could never replace it.
Cargo handles so much responsibility outside of system packages that they couldn't even come close to replicating the utility.
Checking language versions, editions, compiling macros and sources, cross-compiling for foreign architectures, linking, handling upgrades, transient dependency versioning, handling conflicts, feature gating, optional compilation, custom linting and strictness, installing sidecars and cli utilities, etc. etc.
Once it's hermetic and namespaced, cargo will be better than apt / yum / etc. They're not really performing the same tasks, but cargo is just so damned good at such important things that it's hard to imagine a better tool.
There was also the python 2.7 problem for a long time, thanks to this model, it couldn't be updated quickly and developers, including the OS developers, became dependent on it being there by default, and built things around it.
Then when it EOL'd, it left alot of people exposed to vulnerabilities and was quite the mess to update.
You mean 1 in history vs several every week? Looks to me that there actually is a difference.
The npm community is too big that one can never discard it for frontend development.
NPM forcing a human to click a button on release would have solved a lot of this stuff. So would have many other mitigations.
it can't be anyone, because you're essentially delegating trust.
no way there's enough trustworthy volunteers (and how do you vet them all?)
and who's going to pay them if they're not volunteers?
(It makes sense that they'd target GHA first, since that's where the majority of their users probably are. But the technique itself is fundamentally platform agnostic and interoperable.)
The software is too fine-grained. Too many (way too many) packages from small projects or obscure single authors doing way too many things that are being picked up for one trivial feature. That's just never going to work. If you don't know who's writing your software the answer will always end up being "Your Enemies" at some point.
And the solution is to stop the madness. Conglomerate the development. No more tiny things. Use big packages[1] from projects with recognized governance. Audit their releases and inclusion in the repository from a separate project with its own validation and testing. No more letting the bad guys push a button to publish.
Which is to say: this needs to be Debian. Or some other Linux distro. But really the best thing is for the JS community (PyPI and Cargo are dancing on the edge of madness too) to abandon its mistake and move everything into a bunch of Debian packages. Won't happen, but it's the solution nonetheless.
[1] c.f. the stuff done under the Apache banner, or C++ Boost, etc...
Give it a few more decades, hopefully it'll be boring by then, the same way, say, making a house is boring.
The government will mostly ban it to keep prices high?
Stability (in the "it doesn't change" sense) is underrated.
The only tricky bit would be to disallow approval own pull request when using trusted publishing. That should fall back to requiring 2FA
Is there any particular reasoning for the whitelist approach? Standing on the sidelines it appears wholly unnecessary to me. Authentication that an artifact came from a given CI system seems orthogonal to the question of how much trust you place in a given CI system.
Also, from an implementation standpoint it is probably easier to make a system that just works for a handful of OIDC providers, than a more general solution. In particular, a general solution would require having a UI and maybe an API for registering NPM as a service provider for an identity provider of the package owner's choice.
(This is also only half the problem: the Relying Party also needs to be confident that the IdP they're relying on is actually competent, i.e. can be trusted to maintain a private key, operationalize its rotation, etc. That's not something that can easily be automated.)
Go did a lot wrong. It was just awful before they added Go modules. But it’s puzzling to me to understand why as a community and ecosystem its 3rd party dependencies seem so much less bloated. Part of it I think is because the standard library is pretty expansive. Part of it is because of things like golang.org/x. But there’s also a lot of corporate maintainers - and I feel like part of that is because packages are namespaces to the repository - which itself is namespaced to ownership. Technically that isn’t even a requirement - but the community adopted it pretty evenly - and it makes me wonder why others haven’t.
In terms of coordination, I don't see any obvious Bun contributors in a quick skim [1], but it seems open to contribution and is MIT licensed.
[0] https://jsr.io/docs/using-packages#adding-a-package
[1] https://github.com/denoland/std/graphs/contributors
> Or at least first party libraries maintained by the JavaScript team?
There is no "JavaScript team".
Technicalities are secondary to those factors.
If Ruby was the only language that ran in the browser, you'd be writing the same rant about Ruby, no matter the stdlib.
Simple enough, things like npm and pip reinvent a naming authority, have no cost associated (so it's weak to sybil attacks), all for not much, what do you get in exchange? You create equality by letting everyone contribute their wonderful packages, even those that don't have 15$/yr? I'm sorry was the previous leading internet mechanism not good and decentralized enough for you?
Java's package naming system is great in design, the biggest vuln in dependencies that I can think of on java was not a supply chain specific vuln, but rather a general weakness of a library (log4j). But maybe someone with more java experience can point to some disadvantage of the java system that explains why we are not all copying that
(Most of these are good things, to be clear!)
How is that leveraged by attackers in practice? Naively I would expect the actual issue to be insufficient sandboxing (network access in particular).
Sandboxing would be a useful layer of defense, but it’s not a trivial one to add to ecosystems where execution on the host is already the norm and assumption.
(I wouldn’t be surprised to learn that a large number of packages in Python do in fact have legitimate network build-time dependencies. But it would be great to actually be able to quantify this so the situation could be improved.)
(Legitimate seems like a gray area to me — it’s common for applications to have a downloadable installer that then bootstraps the actual program, for example. Is this good or bad? I don’t know!)
1- The rules and customs regarding how applications and libraries are installed CAN be different.
2- These customs MUST be different.
3- Libraries should not download code or binaries over the network in contexts where it is expected they be built from source.
4- Applications should have the least amount of network pointers to binaries as possible, it reduces the amount of oversight app stores or antiviruses can have.
I'm not saying it's a malicious pattern, but it doesn't help with distinguishing a malicious from a legitimate pattern. And I think legitimate actors generally want to jump hoops that complicate malicious actors more than legitimate ones.
I don't know about you, but if I'm installing a pip, npm or maven dependency, I expect it to be open source, and if the repo being pulled from is just a pointer to a URL that downloads the actual code, I would feel kind of scammed, I'd probably just report it to Pypi or npm, and would expect them to take it down. And especially if the package can change without changing the actual repo, it would fail an audit from me, it would mean a supply chain vuln wouldn't leave an artifact, it would also mean the build isn't deterministic at all.
You have one or two megalibraries that are like 20 years old and battle tested and haven't really changed in forever.
Then you have a couple specific libraries for your very specific problem.
Then, you pin those versions. You probably even run your own internal repo for artifacts so that you have full control over what code you pull in to your CI
But none of this actually prevents supply chain attacks. What it does is drastically lower their profitability and success.
Lets say you magically gain access to the Spring Boot framework's signing keys. You put out a malicious version that will drop persistent threats and backdoors everywhere it can and pulls out any credit card numbers or whatever it can find. The team behind Spring Boot takes like two weeks to figure it out, notify the breach, and take down the malicious code.
How many actual systems have even pulled that code in? Very few. Even a significant supply chain attack still requires significant luck to breach targets. In NPM land, this is not the case, and tons of things are pulling in the "latest" version of frameworks. You are much more likely to get someone to actually run your malicious code.
In this scenario, if a dependency were to add a "postinstall" script because it was compromised, it would not execute, and the user can review whether it should, greatly reducing the attack surface.
I do understand this is still better than npm right now, but it's still broken.
I had 25 million downloads on NPM last year. Not a huge amount compared to the big libs, but OTOH, people actually use my stuff. For this I have received exactly $0 (if they were Spotify or YouTube streams I would realistically be looking at ~$100,000).
I propose that we have two NPMs. A non-commercial NPM that is 100% use at your own risk, and a commerical NPM that has various guarantees that authors and maintainers are paid to uphold.
Every time someone pulls/messes with/uploads malware to NPM, people complain and blame NPM.
Every time NPM takes steps to prevent pulling/messing with/uploading malware to NPM, people complain and blame NPM.
I don't think splitting NPM will change that. Current NPM is already the "100% use at your own risk" NPM and still people complain when a piece of protestware breaks their build.
Before you were never to use a public version of something as-is. Each company was having their own corporate repository with each new version of dependencies being carefully curated before being added to the repository.
Normally you should not update anything without at least looking at the release note differential to understand why you update but nowadays people add or update whatever package without even looking.
You just have to look at how many downloads got typosquated clones of famous projects.
For me it is even bad for the whole ecosystem as everyone is doing that, the one still doing that are at odd, slower and less nimble. And so there is a dumping with no one anymore committed to pay the cost of having serious software practices.
In my opinion, node, npm and the js ecosystem are responsible in a big part of the current situation. Pushing people and newbies to wrong practices. Cf all the "is-*x packages...
The culture made sense in the early days when it was a bunch of random nerds helping each other out and having fun. Now the freeloaders have managed to hijack it and inject themselves into it.
They also weaponise the culture against the devs by shaming them for wanting money for their software.
Many companies spend thousands of dollars every month on all sorts of things without much thought. But good luck getting a one-time $100 license fee out of them for some critical library that their whole product depends on.
Personally I'd like to see the "give stuff to them for free then beg and pray for donations" culture end.
We need to establish a balance based on the commercial value that is being provided.
For example I want licensing to be based on the size and scale of the user (non-commercial user, tiny commercial user, small business, medium business, massive enterprise).
It's absurd for a multi-million company to leech off a random dev for free.
No one if forced to use these licences. Even some FOSS licences such as AGPL will not be used by many companies (even the GPL where its software that is distributed to users). You could use a FOSS license and add an exemption for non-commercial use, or use a non-FOSS license that is free for non-commercial use or small businesses.
On the other hand a lot of people choose permissive licenses. I assume they are happy to do so.
Alternatively, we can accept that there will be fewer libraries because some volunteers won't do the extra work for free. Arguably there are too many libraries already so maybe a contraction in the size of the ecosystem would be a net positive.
Lots of people chase downloads on NPM. It's their validation, their youtube subscribers, or their github stars if you will. That's how they get job offers. Or at least they think they do, I don't know if it actually works. There's tons of good software there, but the signal to noise ratio is still rather low.
Given that, I'd rather get paid for including your software as a dependency to my software, boosting your downloads for a long time.
Just kidding, of course. On that last part. But it wouldn't surprise me the least if something like it actually happened. After all, you can buy stars on github just like on any other social media. And that does strange things to the social dynamics.
So yeah… people will always have these workflows which are either stupid or don’t have an easy way to use a lock file. So I’d sure as hell like npm to also take some steps to secure things better.
As far as I know, using a lock file with npm install is both the default behavior and also doesn’t randomly updates things unless you ask it to… though it’s definitely best practice to pin dependencies too
The irony is that trusted publishing pushes everyone toward GitHub Actions, which centralizes risk. If GHA gets compromised, the blast radius is enormous. Meanwhile solo devs who publish from their local machine with 2FA are arguably more secure (smaller attack surface, human in the loop) but are being pushed toward automation.
What I'd like to see: a middle ground where trusted publishing works but requires a 2FA confirmation before the publish actually goes through. Keep the automation, keep the human gate. Best of both worlds.
The staged publishing approach mentioned in the article is a good step - at least you'd catch malicious code before it hits everyone's node_modules.
We were left with a tough choice of moving to Trusted Publishers or allowing a few team members to publish locally with 2FA. We decided on Trusted Publishers because we've had an automated process with review steps for years, but we understand there's still a chance of a hack, so we're just extremely cautious with any PRs right now. Turning on Trusted Publishers was a huge pain with so many package.
The real thing we want for publishing is for us is to be able to continue to use our CI-based publishing setup, with Trusted Publishers, but with a human-in-the-loop 2FA step.
But that's only part of a complete solution. HITL is only guaranteed to slow down malicious code propagating. It doesn't actually protect our project against compromised dependencies, and doesn't really help prevent us from spreading it. All of that is still a manual responsibility of the humans. We need tools to lock down and analyze our dependencies better, and tools to analyze our our packages before publishing. I also want better tools for analyzing and sandboxing 3rd party PRs before running CI. Right now we have HITL there, but we have to manually investigate each PR before running tests.
I understand things need to be safe, but this is a recklessly fast transition.