PyPI Trusted Publishing: Replace long-lived tokens with OIDC uploads
Use PyPI Trusted Publishing to upload packages via short-lived OIDC tokens instead of long-lived API tokens, and verify the trust boundary.
29 May 2026, 16:57 UTC

The problem: long-lived tokens in CI secrets
Many Python projects publish to PyPI from CI by storing a long-lived PyPI API token as a repository secret. If that secret is leaked, anyone can upload new versions to the project. Rotating the token helps after exposure, but the window between leak and discovery is the risk.
PyPI Trusted Publishing removes the long-lived token from CI secrets. Instead, the CI system asks its OpenID Connect (OIDC) provider for a short-lived identity token. PyPI validates that token against a trusted publisher binding you create on the project page. If the binding matches, PyPI grants a scoped upload permission for that single operation.
Requirements
- A PyPI account with owner or admin rights on the target project.
- A CI provider that can mint OIDC tokens for a PyPI audience, such as GitHub Actions, GitLab CI, or CircleCI.
- Permission to add or edit workflow files in the source repository.
- Package build artifacts ready for upload, typically in a
dist/directory.
Smallest suitable design
Create one trusted publisher binding per PyPI project. Bind the upload to a specific repository, branch, environment, or workflow file rather than to the whole account. In GitHub Actions, grant the workflow id-token: write permission only on the jobs that publish, and protect the publishing environment with required reviewers if the project is high-value.
Example GitHub Actions publishing job
Run this in the repository’s .github/workflows/publish.yml. Replace placeholders with your values.
name: Publish to PyPI
on:
release:
types: [published]
permissions:
contents: read
jobs:
publish:
runs-on: ubuntu-latest
environment: pypi-release
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Build package
run: python -m build
- name: Publish to PyPI via Trusted Publishing
uses: pypa/gh-action-pypi-publish@release/v1
Where to run: GitHub Actions, triggered by a release event. Required permissions: repository write access to add the workflow; PyPI project ownership to create the binding. Expected check: the workflow run completes and the new version appears on the PyPI project page. Risk: a compromised repository or workflow file can publish under the project name because PyPI trusts the OIDC issuer assertion.
On PyPI, open the project page, go to Trusted Publishing, and add a publisher. Choose GitHub Actions as the issuer, enter the repository owner and name, and scope the binding to the release branch or environment. PyPI stores the binding; no API token is stored in repository secrets.
Trust and data boundaries
PyPI validates four elements of the OIDC token before accepting an upload:
- Issuer: the OIDC provider that signed the token.
- Audience: the intended recipient, typically PyPI.
- Subject: the workload identity, such as the repository and branch.
- Expiration: the token must not be expired.
Trusted Publishing covers automated package uploads only. Manual uploads with twine and project administration still require separate PyPI account authentication. Revoking a binding blocks future CI uploads immediately; it does not rotate your PyPI password or existing API tokens.
The trust boundary is the CI repository and its workflow permissions, not just the PyPI project. If a contributor can push to the bound branch or edit the workflow, they can effectively publish to PyPI.
Operational checks
- Open the PyPI project page, navigate to Trusted Publishing settings, and confirm the issuer, audience, and binding status for the target repository.
- Trigger a test CI upload using an OIDC token and confirm PyPI accepts the package or returns a clear authorization error.
- Revoke the trusted publisher binding and verify that the same CI upload is rejected by PyPI.
- Confirm that
id-token: writeis not granted to pull-request workflows from forks.
Failure modes
- Clock skew between the CI provider and PyPI, or very short OIDC token lifetimes, can cause intermittent upload failures.
- Changing repository visibility, forking the repository, or altering workflow permissions can shift the effective trust boundary without an explicit PyPI configuration change.
- A compromised trusted repository or CI workflow can publish malicious versions under the project name.
- Bindings are project-specific, so organizations with many packages need repetitive configuration or automated provisioning.
Conditions that change the design
If you manage more than a handful of projects, manual binding creation becomes operational toil. Consider provisioning bindings through the PyPI API or a configuration repository that tracks which repositories are trusted for which projects.
If you need to publish from multiple environments—such as test and production—create separate bindings for each environment and scope workflow permissions accordingly. Do not use one binding for both a feature-branch workflow and a release workflow.
If your CI provider is not in the supported issuer list, or if your organization prohibits OIDC providers from asserting identity to PyPI, continue using scoped API tokens with strict rotation and secret-scanning controls.
Limitation and verification
This draft describes established Trusted Publishing behavior, but PyPI’s issuer list and settings UI may change. Before relying on this design, verify the current supported issuers and binding steps on pypi.org, and run the revocation test above in a non-critical project.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.