你不该信任“可信发布”
ENOSUCHBLOG
Programming, philosophy, pedaling.
• Home (https://blog.yossarian.net/)
• Tags (https://blog.yossarian.net/tags)
• Series (https://blog.yossarian.net/series)
• Favorites (https://blog.yossarian.net/favorites)
• Archive (https://blog.yossarian.net/archive)
• Main Site (https://yossarian.net/)
• TILs (https://yossarian.net/til)
•
Jul 7, 2026 Tags: oss (https://blog.yossarian.net/tags#oss), python (https://blog.yossarian.net/tags#python), security (https://blog.yossarian.net/tags#security)
•
…because Trusted Publishing (https://docs.pypi.org/trusted-publishers/) isn’t for you (or me) to trust! It’s for the machines.
Trusted Publishing is an authentication scheme that involves machine-to-machine trust. If you find yourself arguing (or for!) against Trusted Publishing on the basis that you (a human) can’t (or can) trust it then you’re making a category error, one that PyPI is careful to help you not make.
I wrote about this indirectly about two years ago (https://blog.yossarian.net/2024/11/18/Security-means-securing-people-where-they-are), but I left this observation implicit rather than laying it out explicitly. This is an attempt to correct that mistake.
Quick recap# (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#quick-recap)
“Trusted Publishing” is a term of art that PyPI (https://pypi.org/) uses to describe a form of authentication on top of OpenID Connect (OIDC (https://openid.net/developers/how-connect-works/)) federation. PyPI released it in 20231 (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:dev), and it’s since been adopted widely by other packaging ecosystems (including npm (https://docs.npmjs.com/trusted-publishers), RubyGems (https://guides.rubygems.org/trusted-publishing/), crates.io (https://crates.io/docs/trusted-publishing), and NuGet (https://learn.microsoft.com/en-us/nuget/nuget-org/trusted-publishing)).
The basic observation behind Trusted Publishing is twofold:
1. Long-lived credentials (like index API tokens) are difficult to secure, and are often over-scoped because users find it frustrating to determine the “right” minimal scope and expiry. Consequently, the impact of a compromised or leaked credential can be much broader than the specific system it was stolen from.
2. Many users provision credentials solely for the purpose of putting them in a CI/CD platform, which they then publish from. However, CI/CD platforms often also have identity mechanisms that allow a user to demonstrate control of a specific machine identity2 (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:machine-identity) via OIDC.
Trusted Publishing combines these into an authentication scheme: the user performs a one-time registration of their “Trusted Publisher” (their CI/CD machine identity) with their package index. Then, whenever the CI/CD presents an identity token, the package index verifies it and issues a corresponding, minimally scoped, short-lived publishing credential.
This can be hard to visualize; Seth Larson (https://sethmlarson.dev/)’s diagram is instructive:
(Source: OpenSSF (https://repos.openssf.org/trusted-publishers-for-all-package-repositories.html))
Broadly speaking, this approach to short-lived, self-scoping credentials has been a huge success: we’ve found that users3 (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:pypi) prefer not to manually manage credentials when they don’t have to, and that large Open Source projects and companies alike both like the property of their publishing being tied to their source identity rather than any individual project maintainer.
Of course, Trusted Publishing is not perfect: it has a complicated data model4 (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:datamodel), requires bespoke treatment for each OIDC provider that an index chooses to support5 (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:support), and (of course) can still be compromised (since, at the end of the day, it’s still an authentication scheme and there needs to be a credential somewhere in the flow)6 (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:compromised).
Despite these qualifications, I think Trusted Publishing has been a net win for packaging ecosystems that adopt it. As mentioned in the earlier post (https://blog.yossarian.net/2024/11/18/Security-means-securing-people-where-they-are), security is a matter of identifying and protecting the areas where your users congregate their trust (their “watering holes”), and Trusted Publishing accomplishes that by reducing the number of long-lived, over-scoped credentials that users are exposed to.
What of trust?# (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#what-of-trust)
Thus far, everything I’ve written makes Trusted Publishing sound pretty good, if not a silver bullet.
So, why the emphasis on not trusting it?
This is an important subtlety: Trusted Publishing is just an authentication method. The only thing Trusted Publishing does is establish a trust relationship between an external machine identity (like a CI/CD workflow) and a package identity on an index for the purpose of authenticating an upload; it does not tell a user that a package is safe to use, high quality, or anything else.
You can prove this to yourself easily: PyPI is a public index (by design), which means that anybody can upload to it, and anybody can use a Trusted Publisher to upload7 (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:can). Because anybody can use one, they can be used to upload anything, including malware or vulnerable code. They are exactly like API tokens (PyPI’s other upload authentication method) in this regard.
PyPI is _very careful_8 (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fn:careful) to not mislead users into believing that they can or should trust a package based on its Trusted Publishing status. If you go to a project’s page on PyPI, you’ll notice that there is no “magic green checkmark” signaling a project’s Trusted Publishing status.
Using zizmor (https://pypi.org/project/zizmor/) as an example:
Observe that the only “green checkmark” for user-controlled state on this page is for links that PyPI can prove came from the same source as the package itself. These links, too, are intentionally not described as “trusted”; as the PyPI docs explain (https://docs.pypi.org/project_metadata/#verified-details):
A URL being verified only attests that the URL is under control of the PyPI package owner at the time of verification, and does not imply any additional safety about that URL or any other relationship to the project in question.
If you do want to see the Trusted Publishing status of a particular file on PyPI you’ll need to dig for it. You’ll find it ignominiously rendered as a simple “Yes/No” deep in the file details (https://pypi.org/project/zizmor/#zizmor-1.26.1.tar.gz):
I say “ignominiously” because you can see how little effort has been placed into rendering the “file metadata” section, because it’s not important trust information. We don’t even bother to properly render the blob of JSON that comes from the uploading client’s user agent!
Here, too, you can see that no normative statement of trust is applied to the Trusted Publishing status: there’s a boolean state, and nothing whatsoever to imply that a user can or should trust a project more or less because of that state.
Summary# (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#summary)
Trusted Publishing is a mechanism for establishing trust between an external machine identity (like a CI/CD workflow) and one or more projects on a package index/registry. The “trust” in “Trusted Publishing” refers to that trust relationship, and not to anything else.
It is not, and cannot be, a signal for package trust or quality. You cannot use it to determine whether a package is safe or “good,” and PyPI consciously stymies attempts to misuse it for that purpose by not rendering it as a “green checkmark” or anything else of the sort.
Or as another framing: Trusted Publishing is just a form of authentication. It doesn’t tell you anything other than that an upload was authenticated, which all uploads to PyPI are.
With apologies to Anatole France (https://www.goodreads.com/quotes/361132-the-law-in-its-majestic-equality-forbids-rich-and-poor):
Trusted Publishing, in its majestic equality, permits seasoned maintainer and script kiddy alike to publish malware, to release slop for HN karma, and to obtain security advisories for embarassing flaws.
Afterword# (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#afterword)
This post is about Trusted Publishing, _not_attestations (https://docs.pypi.org/attestations/). Attestations are a technically distinct topic on PyPI that (currently) also use OIDC machine identities, but are simiarly not a signal of trust.
To analogize: an attestation is effectively a signature on top of a machine identity, but anybody can upload to PyPI and therefore anybody can sign with any machine identity they control. The presence of an attestation is not guaranteed by the presence of a Trusted Publisher, and similarly does not imply any particular end-user trust until you separately establish trust in that identity.
This, too, is explicitly documented (https://docs.pypi.org/attestations/security-model/#trustworthiness).
•
1. To my recollection, we spent a good part of 2022 designing and implementing it.↩ (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:dev)
2. The concept of a machine identity can be difficult to develop an intuition for, in no small part because what “counts” as a sufficient identity varies wildly by OIDC IdP.
In the context of GitHub Actions (and Trusted Publishing specifically), the “machine identity” is effectively a tuple of cryptographically verifiable claims about the GitHub Actions workflow that issued the OIDC identity token.
For example, the identity (github, hello-world, publish.yml, pypi.org) might correspond to a GitHub Actions workflow names publish.yml, running underneath the github/hello-world repository, running with an environment named pypi.org.↩ (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:machine-identity)
3. At least on PyPI. I don’t have direct evidence from other ecosystems.↩ (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:pypi)
4. PyPI has “pending publishers” as a way to solve the chicken-and-egg problem with nonexistent projects, but it’s not great: it complicates the data model significantly, and users find it more confusing than “regular” Trusted Publishing.↩ (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:datamodel)
5. This is primarily a consequence of OIDC’s own (in my opinion, excessive) flexibility: outside of a common subset of claims, OIDC IdPs are free to put anything in their claim set.
The consequence of this is that machine identities are not fungible across OIDC IdPs, and indices typically need to handle each IdP’s unique “shape” on a case-by-case basis. This is one of the reasons (but not the only reason) why PyPI adds new Trusted Publishing providers so slowly.↩ (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:support)
6. One of the themes of 2026 so far is attackers realizing that they can exfiltrate a Trusted Publishing credential (or its “seed” credential, the OIDC ID token), typically as a pivot after compromising a CI/CD workflow.
This is similar to what would happen if the workflow had a long-lived credential provisioned against it, except that the Trusted Publishing credential doesn’t have the same scoping and longevity risks.
Still, it’s not ideal, and PyPI has taken steps to mitigate the (external) problem of CI/CD compromise by e.g. refusing token exchanges when the machine identity corresponds to a easily exploited trigger like pull_request_target.↩ (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:compromised)
7. Anybody can use a Trusted Publisher on PyPI, but they do not have to and will never have to.
It is simply infeasible as an engineering matter (in addition to being technically and socially undesirable) to force users into Trusted Publishing. It will always be optional, and cannot be any other way.↩ (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:can)
8. I believe most package indices are similarly careful, although I have less direct experience with them.↩ (https://blog.yossarian.net/2026/07/07/You-shouldnt-trust-trusted-publishing#fnref:careful)
•