This PEP proposes a standard mechanism through which arbitraryPython package indices can support “Trusted Publishing,” a misuse-resistantcredential exchange scheme already implemented by the Python Package Index(PyPI).
The mechanism proposed in this PEP is designed to encapsulate PyPI’sexisting implementationof Trusted Publishing, while allowing other indices to implement the samescheme in a manner that is discoverable by and interoperable with existingPython package uploading clients.
“Trusted Publishing” is PyPI’s term of art for using theOpenID Connect (OIDC) standardto exchange a short-livedidentity credential from a trustedthird-party service (like a CI/CD or cloud provider) for a short-lived,minimally-scopedupload credential that can be used to publishto the index.
Trusted Publishing was originally designed and enabled on PyPI in 2023 asa non-standard (PyPI-specific) feature, much like the existingupload API. It has seenwidespread adoption in that capacity: over one million files have been publishedto PyPI using a Trusted Publisher (as of September 2025), representingapproximately one in every eight files uploaded to PyPI since becomingavailable. Additionally, PyPI’s design has inspired similar designs in theRust (crates.io),Ruby (RubyGems), andJavaScript (npm) ecosystems.
The absence of a standard for Trusted Publishing presents a long-termimpediment for adoption: third-party indices (i.e. those other thanPyPI and TestPyPI) cannot easily implement Trusted Publishing withoutreferencing PyPI’s unstandardized design. This in turn poses a long-termmaturity risk similar to that of the unstandardized upload API: package uploadclients (likeTwine anduv) must either accept behavioral differencesbetween indices (leading to an accretion of hacks) or continue to rejectnon-PyPI implementations of Trusted Publishing.
The lack of an existing standard for Trusted Publishing is the primaryrationale for this PEP.
The design proposed in this PEP closely follows PyPI’s existing implementation,with an added layer ofdiscoverythat enables uploading clients to determine whether an arbitrary indexsupports Trusted Publishing without making PyPI-specific assumptions.
The rationale for this design is as follows:
In sum, the rationale for this PEP is to standardize PyPI’s existinginterfacesand make them discoverable while allowing index hoststhat don’t match PyPI’s topology to implement Trusted Publishing.
This PEP’s specification contains two parts:
Unless explicitly stated otherwise, the following constraintsapply to all parts of this PEP’s specification:
httpsscheme, be some variant of a local loopback (localhost,127.0.0.1, etc.), or otherwise be considereda priori trustworthyin the context of the interaction (e.g. an internal network).Uploading clientsMUST reject any URLs that do not meet this constraint.
In practice, this means that a discovery request tohttps://upload.example.com/.well-known/pytp/{key} can onlyreturn URLs with theupload.example.com host.
Accept:application/vnd.pypi.pytp.v1+json header. In the absence ofanAccept header, the receiving serverMUST behave as if this headerwere present.Receiving serversSHOULD respond with a406NotAcceptablestatus code if any otherAccept header is present.
application/problem+json media type in its responses.All Python package uploading is currently “endpoint driven,” in the senseuploading clients (liketwine anduv) are given an upload URL (andnot merely a domain name).
For example, to upload to PyPI, uploading clients are expected to connecttohttps://upload.pypi.org/legacy/.
The discovery mechanism proposed below takes advantage of this fact toallow single domains to advertise support for multiple indices(and their corresponding upload endpoints).
The discovery mechanism is as follows:
https://upload.example.com/legacy/.For the above example, the path component is/legacy/.
For the above example, the discovery key is0cace9579789849db6e16d48df183951c8f17582200d84bc93c7678d6c8f78a7.[1]
/.well-known/pytp/and the discovery key.For the above example, the discovery URL ishttps://upload.example.com/.well-known/pytp/af030c06750716b1b35852298fe852b90def13dcbd012a5fe5148470f1206bfc.
200OK status code and a bodycontaining a JSON object if the index supports Trusted Publishingfor the given upload URL. The JSON objectMUST contain the followingfields:audience-endpoint: a string containing the URL of the OIDCaudience endpoint to be used during token exchange.token-mint-endpoint: a string containing the URL of thetoken minting endpoint to be used during token exchange.For the above example, a valid response body would be:
{"audience-endpoint":"https://upload.example.com/_/oidc/audience","token-mint-endpoint":"https://upload.example.com/_/oidc/mint-token"}
If the server does not support Trusted Publishing for the givenupload URL, itMUST respond with a404NotFound status code.
ServersMAY additionally respond with any other standard HTTPerror code in the 400 or 500 range to indicate an appropriate errorcondition.
Once an uploading client has performed a successfuldiscovery flow, it can proceed to performthe actual Trusted Publishing token exchange.
Token exchange occurs in three steps:
To retrieve the expected OIDC audience, the uploading client performsan HTTP GET request to theaudience endpoint obtained duringdiscovery.
On success, the server responds with a200OK status code and a bodycontaining a JSON object with the following field:
audience: a string containing the expected OIDC audience.On failure, the serverMUST respond with a standard HTTPerror code in the 400 or 500 range to indicate the appropriate error condition.
After the uploading client has performedaudience retrieval and obtained anidentity credential from the Trusted Publishing provider, it canproceed to mint an upload credential.
To mint an upload credential, the uploading client performsan HTTP POST request to thetoken minting endpoint obtained duringdiscovery. The payload of thePOST requestMUST be a JSON object containing the following:
token: a string containing the identity credentialobtained from the Trusted Publishing provider.On success, the server responds with a200OK status code and a bodycontaining a JSON object with the following fields:
token: a string containing the upload credential. The formatof the upload credential is implementation-defined and index-specific.expires: anoptional integer containing a Unix timestampindicating when the upload credential expires. If this field is notpresent, the uploading clientMAY assume an expiration pointof not more than 15 minutes (900 seconds) after the time oftheir request.The serverMUST NOT issue temporary upload credentialsthat expire in less than 15 minutes (900 seconds) or more than6 hours (21,600 seconds) from the time of the request.
The maximum expiry time of 6 hours is chosen to match common runtime limitson popular CI/CD providers like GitHub Actions.
The uploading clientMAY use this time (or the minimum specifiedabove) to determine when to refresh the upload credential, if needed.
On failure, the serverMUST respond with any standard HTTPerror code in the 400 or 500 range to indicate the appropriate error condition.
This PEP seeks to improve the security and transparency of the Python packagingecosystem by formally standardizing the Trusted Publishing flow alreadyused by PyPI.
This PEP does not identify any positive or negative security implicationsassociated with the Trusted Publishing discovery or exchange flows themselves.
Separately from the flows, Trusted Publishingitself has asecurity model on PyPIand is considered to be a more secure alternative to long-livedAPI tokens or passwords. The primary positive security implications ofTrusted Publishing are:
This PEP does not change any existing behavior and is fully backwards compatiblewith existing upload clients and indices.
Existing clients that perform PyPI’s non-standard Trusted Publishingupload flow will continue to work as before, as will existing uploadsto all indices that do not implement Trusted Publishing.
This PEP is aformalization of Trusted Publishing, which has alreadyseen widespread adoption in the Python packaging ecosystem. That adoptionhas been accompanied by a variety of educational resources onadopting Trusted Publishing as an end user, including:
This PEP’s discovery mechanism uses the.well-known location schemedefined inRFC 8615. This scheme is widely adopted by machine-to-machineprotocols, including OpenID Connect itself (forOpenID Connect Discovery).
An alternative idea considered was to use a “lateral” discovery mechanism,in which the uploading client would attempt discovery by constructing aadjacent path relative to the upload URL. For example, forhttps://upload.example.com/legacy/, the uploading client wouldattempt to discover Trusted Publishing support athttps://upload.example.com/legacy/pytp (or some equivalent).
The advantage of this approach is that it doesn’t require index operatorsto have control over their (sub-)domain, which the.well-known schemeexpects (as well-known URIs can only be served from the root of a domain).
However, this approach also has downsides:
/legacy/{*} for other purposes..well-known scheme. Developinga custom location scheme here would require additional informationalmaterials for server administrators and operators who are accustomedto the.well-known scheme.Another alternative idea considered was the perform “implicit” discovery,similar to what PyPI currently does for Trusted Publishing: instead of anexplicitdiscovery step, the uploading client could jumpstraight to attempting the audience and token minting steps, andhandle any errors that arise.
The advantage of this approach is simplicity: it eliminates the networkround-trip needed for the discovery step, and eliminates the indirectionof obtaining the audience and token minting endpoints from the discoveryresponse.
This approach too has downsides:
>>>importhashlib>>>path="/legacy/">>>key=hashlib.sha256(path.encode("utf-8")).hexdigest()>>>print(key)0cace9579789849db6e16d48df183951c8f17582200d84bc93c7678d6c8f78a7
This document is placed in the public domain or under theCC0-1.0-Universal license, whichever is more permissive.
Source:https://github.com/python/peps/blob/main/peps/pep-0807.rst
Last modified:2025-11-03 20:18:50 GMT