Architecting Token-less Package Deployment with PyPI Trusted Publishing
Learn how to eliminate long-lived secrets in PyPI deployments using Trusted Publishing. This guide covers the OIDC architecture, trust boundaries, and configuration for token-less uploads.
24 Sept 2025, 00:10 UTC

The Problem: Long-Lived Secret Leakage
Traditional package publishing relies on API tokens stored as secrets in CI/CD environments. If a repository is compromised or a developer accidentally logs a secret, the attacker gains persistent access to the project's PyPI distribution. Rotating these tokens is a manual, error-prone process that often leads to deployment downtime.
The takeaway: Trusted Publishing replaces long-lived secrets with OpenID Connect (OIDC) identity federation. Instead of storing a password, PyPI trusts a specific identity provider (IdP) to vouch for the origin of the request, eliminating the need for static secrets in your CI/CD pipeline.
Requirements for Implementation
To implement this architecture, you need three components:
- An OIDC-Compliant Provider: A platform like GitHub Actions or GitLab CI that can issue JSON Web Tokens (JWTs) containing specific claims about the workflow.
- PyPI Project Configuration: The project owner must enable Trusted Publishing in the PyPI account settings, mapping a specific provider and repository to the project.
- A Compatible Publisher: A tool (such as
pypa/gh-action-pypi-publish) capable of requesting an OIDC token from the environment and exchanging it for a short-lived PyPI session token.
Minimal Design and Logic Flow
The system operates on a trust-exchange model rather than a shared-secret model. The minimal design follows this sequence:
- Token Issuance: The CI runner requests a JWT from its IdP. This token includes an
aud(audience) claim set topypi.organd asub(subject) claim identifying the specific repository and workflow. - Verification: PyPI receives the JWT and fetches the IdP's public keys via a well-known configuration endpoint. It verifies the cryptographic signature of the JWT.
- Claim Validation: PyPI checks that the token has not expired and that the
subclaim matches the publisher pattern configured in the project settings. - Scoped Access: Upon successful validation, PyPI issues a short-lived, scoped API token valid only for the duration of that specific upload operation.
Trust and Data Boundaries
The architecture defines a clear boundary between identity verification and artifact storage:
- The Identity Boundary: The OIDC provider is the source of truth for who is requesting access. PyPI treats the incoming JWT as untrusted input until the signature is verified against the provider's public keys.
- The Storage Boundary: Once the identity is verified, the uploaded package enters the storage boundary. The trust established via OIDC does not bypass security scanning; packages are still processed for malware and integrity before becoming available to users.
Operational Configuration Example
For a GitHub Actions environment, the configuration requires specific permissions to allow the runner to request the OIDC token. This is defined in the workflow YAML file.
# Required permissions for OIDC token exchange
permissions:
id-token: write # Necessary to request the JWT
contents: read # Necessary to checkout the code
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
# No password or token is provided here
# The action automatically handles the OIDC exchange
repository-url: https://upload.pypi.org/legacy/ # Use test.pypi.org for verification
Execution Context: Run this within a GitHub Actions workflow. The id-token: write permission is the critical security gate; without it, the runner cannot generate the JWT required by PyPI.
Failure Modes and Diagnostics
| Failure Scenario | Root Cause | Diagnostic Check |
|---|---|---|
| Authentication Error (401) | IdP signing key rotation or network failure fetching keys. | Check PyPI status page or verify IdP's .well-known/openid-configuration endpoint. |
| Forbidden (403) | Mismatch between the JWT sub claim and PyPI settings. |
Verify the repository name and workflow filename in PyPI's Trusted Publishing settings. |
| Permission Denied | Missing id-token: write in workflow permissions. |
Inspect CI logs for "Unable to fetch OIDC token" errors. |
Design Constraints and Triggers for Change
This design is optimal for automated CI/CD pipelines but has limitations:
- Manual Uploads: Trusted Publishing does not replace the need for API tokens for developers performing manual uploads from local machines.
- Integrity: This architecture secures the upload path, not the package content. Consumers should still use hashes or signatures to verify the downloaded artifact.
A change in this design would be required if the project moves to a self-hosted identity provider that does not support the OIDC standard or if PyPI moves toward a different federation protocol (e.g., SPIFFE/SPIRE).
Verification of Result
To verify the implementation without risking a production release, use test.pypi.org. After a successful upload, the project owner can inspect the PyPI audit logs. A successful Trusted Publishing event will show the issuer (e.g., GitHub) and the specific OIDC subject, confirming that no long-lived API token was used for the transaction.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.