From d85368333f1255feca4cf81706cb0d4740f1f77c Mon Sep 17 00:00:00 2001 From: Alexey Shalaev <75322386+AlexeyShalaev@users.noreply.github.com> Date: Fri, 16 Oct 2026 18:52:59 +0400 Subject: [PATCH] feat: releases ship with PEP 740 attestations uv publish uploads attestations it is given but does not create them, so no release in the org had provenance on PyPI, although the publish job already holds the OIDC token Sigstore signs with. The publish job now runs pypi-attestations sign over the built wheel and sdist before uploading. It writes .publish.attestation next to each, and uv publish, with its default dist/* file set, sends them along. PyPI verifies each attestation against the Trusted Publisher and rejects the upload if one does not match, so a broken signature fails the release instead of shipping without provenance. Closes #13 --- template/.github/workflows/publish.yml.jinja | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/template/.github/workflows/publish.yml.jinja b/template/.github/workflows/publish.yml.jinja index 2c093ac..41e0dfe 100644 --- a/template/.github/workflows/publish.yml.jinja +++ b/template/.github/workflows/publish.yml.jinja @@ -69,6 +69,13 @@ jobs: echo "__version__ = \"$VERSION\" # x-release-please-version" > {{ package_name }}/__version__.py - run: uv build - # --check-url: a run repeated after a failure skips what is already on the - # index instead of failing on it. + # PEP 740 provenance. uv publish uploads attestations but does not make + # them: this signs each distribution with the job's OIDC identity + # (Sigstore) into .publish.attestation, and PyPI rejects an upload + # whose attestation does not match the Trusted Publisher. + - run: uvx --from 'pypi-attestations>=0.0.30' pypi-attestations sign dist/*.whl dist/*.tar.gz + # No file arguments: uv's default, dist/*, takes in the .attestation files + # too; naming the distributions would upload them without their + # attestations. --check-url: a run repeated after a failure skips what is + # already on the index instead of failing on it. - run: uv publish --check-url https://pypi.org/simple/{{ project_slug }}/