Skip to content

ci(release): migrate PyPI publish to Trusted Publishing + attestations#4249

Merged
Wauplin merged 2 commits into
mainfrom
ci/pypi-trusted-publishing
May 22, 2026
Merged

ci(release): migrate PyPI publish to Trusted Publishing + attestations#4249
Wauplin merged 2 commits into
mainfrom
ci/pypi-trusted-publishing

Conversation

@XciD

@XciD XciD commented May 21, 2026

Copy link
Copy Markdown
Member

Summary

Migrates the publish-pypi and publish-hf-cli jobs in release.yml from long-lived PyPI API tokens (twine upload -p $PYPI_TOKEN) to PyPI Trusted Publishing (OIDC) via pypa/gh-action-pypi-publish, turns on PEP 740 / Sigstore attestations, and removes the now-redundant legacy tag-triggered publish workflows.

This closes the largest supply-chain gap surfaced by huggingface_hub-1.16.1 showing attestations: None on PyPI.

What changes

In .github/workflows/release.yml:

  • publish-pypi and publish-hf-cli
    • Replace twine upload with pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b (v1.14.0, SHA-pinned).
    • Add permissions: id-token: write and attestations: write (job-scoped, no escalation elsewhere).
    • Add environment: pypi so the publish step is gated behind required reviewers.
    • Build with PEP 517 (python -m build) instead of the deprecated python setup.py sdist bdist_wheel.
    • Drop pip install setuptools wheel twine, drop the PYPI_TOKEN_DIST_* env var.
  • Header comment: remove the PYPI_TOKEN_DIST_* entries from the required-secrets list, document the new Trusted Publisher / Environment requirements, drop the stale reference to the deleted fallback workflows.

Delete:

  • .github/workflows/python-release.yml
  • .github/workflows/python-release-hf.yml

These were the pre-release.yml publish path, triggered by push: tags: v* with PYPI_TOKEN_DIST_* + twine. Keeping them around would let anyone with write access bypass the new pypi environment gate by pushing a tag with a PAT that cascades events. The "manual fallback" they offered is already covered by re-running the failed publish job from the Actions UI.

Already done outside the PR

  • GitHub Environment pypi created on the repo with required reviewers (Wauplin, hanouticelina, XciD, julien-c) and deployment branch policy restricted to v*-release.
  • Trusted Publisher configured on PyPI for huggingface-hub (owner: huggingface, repo: huggingface_hub, workflow: release.yml, env: pypi).
  • Trusted Publisher configured on PyPI for hf, same values.

Post-merge cleanup

After the first successful release on the new flow:

  • Delete the now-unused secrets PYPI_TOKEN_DIST_HUGGINGFACE_HUB and PYPI_TOKEN_DIST_HF.

Why this matters

  • No long-lived PyPI tokens to leak (CI logs, fork, compromised contributor): OIDC tokens are minted per run, scoped to the workflow + env, expire in minutes.
  • Cryptographic provenance (PEP 740 + Sigstore) attached to each wheel and sdist on PyPI, so downstream consumers can verify a release was produced by this workflow.
  • Required reviewer gate on the actual upload step via the GitHub Environment, with no bypass path left.
  • Aligns with PyPA / OpenSSF SLSA recommendations and mitigates the threat class that hit ultralytics (Dec 2024) and PyTorch torchtriton (Dec 2022).

Follow-up PRs (not in scope here)

  • SHA-pin remaining floating refs (trufflesecurity/trufflehog@main, codecov/codecov-action@v5, ...).
  • Enable repo-level Dependabot security updates and secret scanning push protection.
  • Move curl | bash OpenCode installs out of jobs that hold release tokens.
  • Add CodeQL + OpenSSF Scorecard workflows, SECURITY.md, CODEOWNERS, branch protection on v*-release.

Note

Medium Risk
Medium risk because it changes the critical release/publish pipeline (build and upload mechanism, permissions, and environment gating), which could block releases if PyPI/GitHub environment configuration is off.

Overview
PyPI publishing is moved from token-based twine upload to PyPI Trusted Publishing (OIDC) in release.yml for both huggingface-hub and the hf CLI, including job-scoped id-token/attestations permissions, pypi environment gating, and python -m build builds.

Legacy tag-triggered publish workflows are removed (python-release.yml and python-release-hf.yml), and the release workflow docs are updated to reflect the new Trusted Publisher/environment requirements and the lack of token-based fallback.

Reviewed by Cursor Bugbot for commit eb95bb8. Bugbot is set up for automated code reviews on this repo. Configure here.

Switch the `publish-pypi` and `publish-hf-cli` jobs from long-lived API
tokens (`PYPI_TOKEN_DIST_*` + `twine upload`) to PyPI Trusted Publishing
via `pypa/gh-action-pypi-publish` (pinned by SHA, v1.14.0).

Benefits:
- No PyPI secret stored in the repo (OIDC exchange at publish time).
- Each artifact is signed with Sigstore and gets a PEP 740 attestation
  (`attestations: true`), giving downstream consumers a cryptographic
  link back to this workflow run.
- The publish jobs now run in a `pypi` GitHub Environment, which gates
  the actual upload behind required reviewers.
- Build switched from the deprecated `python setup.py sdist bdist_wheel`
  to PEP 517 `python -m build`.

Follow-up steps required before this can run:
1. Configure a Trusted Publisher on PyPI for both `huggingface-hub` and
   `hf`, matching repo `huggingface/huggingface_hub`, workflow
   `release.yml`, and environment `pypi`.
2. Create a `pypi` Environment in the repo settings with required
   reviewers.
3. Once green, delete the now-unused `PYPI_TOKEN_DIST_HUGGINGFACE_HUB`
   and `PYPI_TOKEN_DIST_HF` secrets, and remove or disable the
   tag-triggered fallback workflows (`python-release.yml`,
   `python-release-hf.yml`) which still rely on those tokens.
@bot-ci-comment

Copy link
Copy Markdown

The docs for this PR live here. All of your documentation changes will be reflected on that endpoint. The docs are available until 30 days after the last update.

Delete `python-release.yml` and `python-release-hf.yml`. They were the
pre-`release.yml` publish path, triggered by `push: tags: v*` and using
`PYPI_TOKEN_DIST_*` to upload via twine.

Since the unified `release.yml` took over, these workflows have only
been documented as a "manual fallback" — but the same recovery is
already available by re-running the failed publish job from the Actions
UI.

Keeping them around means anyone with write access can bypass the new
`pypi` environment gate (and the OIDC / attestations flow) by pushing a
tag with a PAT that cascades events. Removing them closes that bypass.

The `PYPI_TOKEN_DIST_HUGGINGFACE_HUB` and `PYPI_TOKEN_DIST_HF` secrets
become fully unused after this PR and the first successful Trusted
Publishing run; they should be deleted from the repo settings then.
@XciD XciD marked this pull request as ready for review May 21, 2026 19:08

@Wauplin Wauplin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking care of this. Will tell you on next release how it went

@Wauplin Wauplin merged commit f155f3d into main May 22, 2026
21 checks passed
@Wauplin Wauplin deleted the ci/pypi-trusted-publishing branch May 22, 2026 08:13
@huggingface-hub-bot

Copy link
Copy Markdown
Contributor

This PR has been shipped as part of the v1.17.0 release.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants