Trusted Publishing with GitHub Actions
Overview
xcookie can generate GitHub Actions workflows that publish to PyPI and TestPyPI using PyPI Trusted Publishing instead of long-lived Twine password secrets.
When trusted publishing is enabled, GitHub Actions authenticates to PyPI using GitHub’s OIDC identity token exchange. This removes the need to store a reusable PyPI API token in GitHub secrets.
This changes only the PyPI authentication path.
It does not remove the optional GPG / OpenTimestamps signing flow. If
enable_gpg = true, the current xcookie flow still expects the GPG signing
materials to be handled by the existing encrypted-key path, so CI_SECRET and
the encrypted GPG files are still relevant.
What is no longer required
When trusted publishing is enabled, these GitHub secrets are no longer required for publishing to PyPI / TestPyPI:
TWINE_USERNAMETWINE_PASSWORDTEST_TWINE_USERNAMETEST_TWINE_PASSWORD
If enable_gpg = true, CI_SECRET is still required for the current GPG
signing flow.
Step-by-step setup
1. Regenerate the GitHub workflow
Regenerate the repository after setting:
[tool.xcookie]
ci_pypi_trusted_publishing = true
Then inspect the generated workflow and confirm that the deploy jobs contain:
uses: pypa/gh-action-pypi-publish@release/v1permissions: id-token: writerepository-url: https://test.pypi.org/legacy/on the test publish job
2. Commit and push the workflow file
Trusted publishing is bound to the exact workflow filename registered on PyPI / TestPyPI. Do not register a workflow path until the final filename and basic structure are stable.
For the current xcookie GitHub workflow layout, the relevant file is:
.github/workflows/release.yml
3. Register a trusted publisher on PyPI
Open the PyPI project settings and configure a trusted publisher for GitHub.
Official docs:
PyPI Trusted Publishers overview: https://docs.pypi.org/trusted-publishers/
PyPI: using a publisher: https://docs.pypi.org/trusted-publishers/using-a-publisher/
PyPI security model: https://docs.pypi.org/trusted-publishers/security-model/
When configuring the publisher, use:
Owner: the GitHub organization or user
Repository name: the repository name
Workflow filename:
.github/workflows/release.yml
If you are using GitHub protected environments, also set the matching environment name in PyPI.
4. Register a trusted publisher on TestPyPI
If you want the generated test_deploy job to publish to TestPyPI, repeat the
same configuration on TestPyPI.
Official docs:
TestPyPI: https://test.pypi.org/
PyPI docs for trusted publishers are also applicable to TestPyPI: https://docs.pypi.org/trusted-publishers/using-a-publisher/
Use the same:
Owner
Repository name
Workflow filename:
.github/workflows/release.yml
5. Run a non-release push and check TestPyPI
The generated test deployment job publishes to TestPyPI on ordinary pushes that are not release-branch pushes and not tag pushes.
Confirm in the GitHub Actions UI that:
the test deploy job runs
the publish step succeeds
no Twine password secret is used
the package appears on TestPyPI
6. Run a release publish and check PyPI
The generated live deployment job publishes to PyPI on:
release-branch pushes, or
tag pushes
After the workflow succeeds, verify:
the package exists on PyPI
GitHub release artifacts contain the expected
.ascand.otsfiles if GPG signing is enabledno reusable PyPI token is stored in GitHub secrets
Secure environment recommendations
Trusted publishing removes long-lived PyPI credentials, but the trusted workflow is still security-sensitive because it controls the build, signing, and publish steps.
Recommended hardening:
1. Use GitHub protected environments
GitHub environments can require manual approval and restrict which branches or tags may deploy.
Official docs:
GitHub OIDC / security hardening: https://docs.github.com/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect
GitHub environments: https://docs.github.com/actions/deployment/targeting-different-environments/using-environments-for-deployment
Recommended environment names:
testpypipypi
2. Keep id-token: write job-scoped
Do not grant OIDC token permissions at workflow scope. Keep them only on the publish jobs.
3. Restrict who can modify the release workflow
The trusted publishing boundary is not just the publish action step. It includes the build and signing steps that produce the exact artifacts that will be published.
Treat changes to those steps as release-surface changes and review them accordingly.
4. Keep the workflow filename stable
Once a trusted publisher is registered, changing the workflow filename requires updating the PyPI / TestPyPI configuration.
Current limitations
Local
actruns cannot emulate the real OIDC publish path.If
enable_gpg = true, the current xcookie implementation still uses the legacy encrypted-key mechanism for signing.Trusted publishing removes PyPI secrets, but it does not by itself remove the GPG secret handling path.