Safeguard
Supply Chain•

Set up a PyPI trusted publisher for GitHub Actions, step by step

Replace a PyPI API token with a trusted publisher: the form fields, the workflow permission, and why a dedicated publish job with an environment helps.

Safeguard Security Team
3 min read

To publish to PyPI without a stored API token, register a trusted publisher on the project's Publishing page (owner, repository, workflow filename, optional environment), then give only the publish job id-token: write and run the PyPA publish action. PyPI trusts the workflow's identity instead of a secret.

Why bother

An API token is a password that sits in your CI secrets. If a pull request workflow, a compromised action or a leaked log exposes it, anyone can upload a release as you. A trusted publisher removes that secret. GitHub issues a short-lived identity token per run, and PyPI accepts it only from the repository and workflow you named.

Step 1: register the publisher

On PyPI, open Your projects, choose Manage for the project, then Publishing in the sidebar. Pick the GitHub Actions tab and fill in:

  • the repository owner,
  • the repository name,
  • the workflow filename (for example release.yml),
  • an environment name, which is optional.

Click Add. The publisher appears at the top of the page. PyPI's docs note that one publisher can be registered to several projects, and one project can have several publishers.

Set the environment even though it is optional. PyPI strongly recommends it, because a GitHub environment lets you add rules such as manual approval before the publish job runs.

Step 2: write the workflow

The permission that makes this work is id-token: write. PyPI says to grant it at job level, and discourages workflow level, so the credential is only exposed where it is needed. A simple layout keeps build and publish apart:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: python -m pip install -U build && python -m build
      - uses: actions/upload-artifact@v4
        with:
          name: dist
          path: dist/

  pypi-publish:
    needs: build
    runs-on: ubuntu-latest
    environment: pypi
    permissions:
      id-token: write
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - uses: pypa/gh-action-pypi-publish@release/v1

No username, password or token appears anywhere. The split is our suggestion rather than a PyPI requirement: build steps run third party tooling, and they never see the publishing permission.

Step 3: remove the old token

Once a release has gone out through the publisher, delete the project token on PyPI and the matching secret in GitHub. Leaving it in place keeps the exact exposure you were trying to remove.

What this does not cover

A trusted publisher proves which workflow uploaded the file. It does not prove the code in that workflow was reviewed. Pair it with branch protection, required reviewers on the environment, and a limited set of people who can edit the workflow file. GitLab CI/CD and Google Cloud publishers exist too, and GitLab support is limited to gitlab.com.

Safeguard's free scan at https://gold.safeguard.sh/scan can show what your own project pulls in, which is a useful check before you publish anything others will depend on.

Quick check

Is the environment field required?

No, it is optional on PyPI, but strongly recommended because it allows extra restrictions such as manual approval.

Where should id-token write go?

On the publish job only. PyPI says job level is strongly recommended and workflow level is discouraged.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.