PyPI Trusted Publishing: OIDC‑Based, Tokenless Uploads for CI/CD
Learn how PyPI’s Trusted Publishing uses OIDC to replace static tokens in CI/CD pipelines, with a step‑by‑step GitHub Actions example, trade‑offs, and a quick verification checklist.
21 Aug 2025, 16:55 UTC

Why Trusted Publishing Matters
When a continuous‑integration pipeline pushes a new Python wheel to PyPI, it traditionally relies on a long‑lived API token or the project’s password stored in CI secrets. That token is a single point of failure: if the secret leaks, an attacker can upload malicious packages. PyPI’s Trusted Publishing feature replaces that static credential with an OIDC identity token, giving you a short‑lived upload token that lives only for the duration of the publish step.
What Trusted Publishing Is
Trusted Publishing is a permission model that lets a project delegate upload rights to an external OIDC provider (GitHub Actions, GitLab CI/CD, etc.). The publisher is scoped to a specific repository, workflow file, environment, and ref pattern. This scoping means that even if a workflow run is compromised, only the defined branches or tags can trigger an upload.
How the Flow Works
- CI runs a workflow that needs to publish a package.
- The workflow requests an OIDC token from the provider (e.g., GitHub).
- PyPI validates the token against the publisher’s configuration and issues a short‑lived
uploadtoken. - The workflow uses that token to call
twine upload(or similar) and pushes the wheel. - After the publish step completes, the upload token expires.
Setting Up a Trusted Publisher in PyPI
1. Navigate to the project’s Settings → Trusted publishing page on PyPI and click “Add trusted publisher.”
2. Choose the provider (e.g., GitHub). You’ll be prompted to provide a repository (e.g., org/repo), workflow (e.g., ci.yml), environment (e.g., production), and ref pattern (e.g., refs/tags/v*). These fields are mandatory; leaving any blank will result in a “publisher not found” error during upload.
3. Click “Create publisher.” PyPI records a JSON Web Token (JWT) claim set that your CI will later present. The publisher is now a “trusted” entity that can generate upload tokens.
Configuring GitHub Actions for Tokenless Uploads
Below is a minimal example of a GitHub Actions workflow that publishes a wheel without storing a PyPI token. The workflow relies on the pypa/gh-action-pypi-publish action, which automatically fetches an OIDC token and exchanges it for an upload token.
name: Publish
on:
push:
tags:
- 'v*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Build wheel
run: pip install build && python -m build
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@v1.10.2
with:
# No token is provided; the action will use OIDC
# Optionally, specify environment if configured
# environment: production
Key points:
- Do not set
passwordorusernamein the action. The action will request an OIDC token automatically. - Ensure the workflow file name and ref pattern match the trusted publisher configuration on PyPI.
- Run the workflow on a protected branch or tag to avoid accidental publishes.
Verifying the Upload Was Authenticated via Trusted Publishing
- After the workflow finishes, go to the project page and click “Release history.”
- Open the newly created release and inspect the “Files” tab. The file hash should match the wheel built in the workflow.
- In the project settings, open the “Trusted publishing” section and confirm that the publisher listed shows the workflow name and tag pattern you used.
- Try a manual upload from your local machine without an OIDC flow; PyPI should reject it with a 401 error, confirming the publisher is not a blanket credential.
Trade‑offs and Limitations
- Scope is a double‑edged sword. Tight scoping reduces blast radius but can block legitimate releases if the ref pattern is too restrictive (e.g., only
refs/tags/v*but you also want to publish frommain). - Trusted publishing does not make your project immutable. Once a file is uploaded for a given version, it cannot be overwritten or deleted. A malicious upload will still require bumping the version number.
- Not all CI providers support OIDC or the exact claim set required by PyPI. If your provider is missing a claim, the publisher will reject the token.
- Local development machines that lack OIDC support must still use traditional API tokens. This means you can’t fully eliminate static credentials unless every developer’s environment supports OIDC.
- Misconfigured publishers can block legitimate releases or, worse, allow uploads from untrusted forks if the ref pattern is too broad.
Actionable Checklist for Your Project
- Add a trusted publisher in PyPI with the correct repository, workflow, environment, and ref pattern.
- Update your CI workflow to use the provider’s OIDC token (no static token).
- Test a publish from a protected tag to confirm the upload succeeds.
- Verify that a local publish without an OIDC token fails.
- Document the publisher settings in your project’s README so future maintainers understand the flow.
Adopting Trusted Publishing gives you a robust, short‑lived credential that tightens security without sacrificing the convenience of automated releases. Just remember: it’s a tool that replaces the token for the upload step, but it doesn’t change PyPI’s immutable release policy or eliminate all supply‑chain risks.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.