(This is a new version of https://discuss.python.org/t/pre-pep-standardizing-trusted-publishing-token-exchange/103057/1, which I fat-fingered before my draft was ready…)
Hello all! I’m opening this up as a pre-PEP discussion thread, in advance of writing a full PEP. I’m hoping to get feedback on the thoughts/ideas above that will inform the ultimate PEP language.
TL;DR: Trusted Publishing has been a huge success on PyPI, which currently exposes it via PyPI-specific APIs. I propose that we standardize a variant of these APIs so that uploader tools can depend on them in a standards-compliant manner. Additionally, I believe a standard variant of these APIs will spur adoption of Trusted Publishing in the broader Python packaging ecosystem, e.g. on private indices that wish to be compatible with standard uploading tools.
Disclaimer
I am a contributor to PyPI, and am a paid employee of Astral, which is developing a service that includes a private registry. I disclaim that I’m interested in this PEP for both PyPI and work reasons
Problem statement
Trusted Publishing has been a huge success on PyPI: over half a million nearly a million files have been uploaded with it (as of today), and it’s now the default upload flow in pypa/gh-action-pypi-publish as well as a first-class flow in twine, uv, pdm, maturin, and so forth. Additionally, Trusted Publishing is the “identity” foundation for Index hosted attestations (formerly known as PEP 740).
Despite this adoption and the fact that it’s referenced in packaging standards and standard (i.e. PyPA) tooling, Trusted Publishing isn’t itself a packaging standard: it’s currently implemented as a (small) set of PyPI-specific REST APIs that tools are consuming much like the unstandardized upload API.
I think this should be remedied with a standard!
Solution statement
I propose that we standardize a variant of the Trusted Publishing token exchange APIs. I think this will have two immediate benefits:
- Upload tooling (twine,
uv publish
, etc.) will be able to rely on these APIs as a packaging standard, rather than as a PyPI implementation detail. This would align with the general goal of reducing the number of non-standard interfaces present in standard Python packaging (with the upload API being a notable remaining one, at least until PEP 694 or similar is accepted). - Indices beyond the current implementation of PyPI will be able to implement these APIs in a standard manner. I believe this has virtuous implications for both PyPI and third-party indices:
- For PyPI itself: an idea that’s been circulated before is supporting multiple “registries” on
pypi.org
itself, i.e. allowing users or organizations to define their own fully isolated registries. This use case would require PyPI to expose separate routes for each “registry,” which in turn means that per-registry uploads would need their own Trusted Publishing routes. Correspondingly, clients will need a standard way to discover those routes. - For third-party indices: I believe users of third-party indices (including the one I work on) would like to use Trusted Publishing like they do with PyPI. A standard that describes how these third-party indices should implement the relevant APIs would insure interoperability with Python’s standard and community-maintained tooling.
- For PyPI itself: an idea that’s been circulated before is supporting multiple “registries” on
Proposal
In terms of concrete details: I believe this PEP should standardize the shape of the Trusted Publishing APIs, not their internal implementation details. This would be similar to other index-specific packaging PEPs, where PyPI’s implementation is not constrained so long as PyPI presents the appropriate shape of API for interoperation.
Roughly, here’s what I propose in the PEP:
-
Trusted Publishing support discovery: right now, upload clients can determine whether an upload-supporting index supports Trusted Publishing by hitting
{domain}/_/oidc/audience
and getting a successful response containing the index’s expected OIDC audience. I propose standardizing a variant of this as{upload_base}/_/oidc/audience
(route name subject to bikeshedding!), whereupload_base
is the “parent” path for the upload endpoint itself. For example, PyPI serves its upload API ashttps://upload.pypi.org/legacy
, so the appropriate audience endpoint would behttps://upload.pypi.org/_/oidc/audience
.The primary advantage of this is that it’s compatible with what PyPI currently does and allows future implementations more flexibility than would be possible with a domain-rooted route – for example, a hypothetical index host could offer audience discovery at
https://example.com/blahblah/{foo,bar}/_/oidc/audience
for two different registries that accept uploads athttps://example.com/blahblah/{foo,bar}/upload
respectively.This is also in-principle compatible with a future standard upload API (e.g. in PEP 694).
-
Trusted Publishing token exchange: I propose standardizing
{upload_base}/_/oidc/mint-token
as a generalization of PyPI’s current{domain}/_/oidc/mint-token
, similar to above. -
Token Publishing token “burning”: I propose this as an optional extension to the current PyPI-specific APIs:
{upload_base}/_/oidc/burn-token
would accept a POST containing a JSON payload containing the temporary API token, which instructs the index to burn (i.e. deactivate) the token. This allows supporting upload clients to slightly enhance Trusted Publishing’s (already) short-lived token model by forcefully deactivating temporary tokens instead of letting them naturally expire.
I propose that the above (except for the “burning” endpoint, which doesn’t exist yet) take the general shape of the current Trusted Publishing APIs on PyPI, which are documented under “the manual way” here at the moment. However, I think there’s area for improvement in these APIs as well, so I welcome vigorous feedback on tweaking these for standards purposes
Other considerations
As proposed above, Trusted Publishing remains fully agnostic to Trusted Publishing providers. In other words, it continues to be up to each uploadable index to determine which OIDC providers it wants to support. I think this would be virtuous to preserve, as OIDC itself is fully federated and IMO a standard here shouldn’t pick “winner” vendors or service providers.
Another open consideration I had is whether Trusted Publishing “discovery” in a standard here should be a bit more structured – two options come to mind:
- We could standardize a
.well-known
endpoint (e.g.{domain}/.well-known/trusted-publishing
) which would then advertise templated endpoints. However, I think this retains some of the downside of having things grounded at the domain level and potentially over-complicates the implementation for PyPI itself. - We could standardize a new discovery endpoint with the same scheme, e.g.
{upload_base}/_/oidc/discover
, which would then advertise appropriate audience, mint-token, etc. URLs in its response. This has the benefit of making discovery more explicit, rather than being a “hope and pray” action on theaudience
endpoint itself. It would also allow flexibility in the audience, mint-token, etc. URLs, i.e. would allow the upload-supporting index to place them somewhere other than{upload_base}
while preserving isolation of different upload endpoints.
Finally, I think {upload_base}/_/oidc/*
is a pretty confusing set of URL paths, and I’m very open to feedback on making them into something much more suitable for a public and standard API . Some random ideas I had:
{upload-base}/trusted-publishing/*
– reuses our standard terminology, avoids the magic underscore{upload-base}/publishing/*
– too generic?
CCing a few people who I expect will be interested in this: @miketheman @dustin @dstufft @sethmlarson @EWDurbin