Applying the SLSA Framework to Build, Sign, Publish, and Verify Python Packages on GitHub
This article demonstrates how to apply the SLSA (Supply chain Levels for Software Artifacts) framework to the Python ecosystem by building clean packages, generating provenance statements, uploading them to PyPI, and verifying the package origin using GitHub Actions and the slsa‑verifier tool.
Software supply‑chain attacks have risen dramatically, prompting Google to propose the SLSA (Supply chain Levels for Software Artifacts) framework. This guide shows how to use SLSA in the Python ecosystem for projects hosted on GitHub, taking a package from build to verification.
Contents
Build a clean Python package
Generate provenance (origin) statements
Upload the package to PyPI
Verify the package provenance
Project references
Build a clean Python package
A pure‑Python wheel and source distribution can be created with the command python3 -m build . The following GitHub Actions job builds the wheel and source distribution and records a SHA‑256 hash for each artifact:
jobs:
build:
steps:
- uses: actions/checkout@...
- uses: actions/setup-python@...
with:
python-version: 3.x
- run: |
# install build, create sdist and wheel
python -m pip install build
python -m build
# collect hashes
cd dist && echo "hashes=$(sha256sum * | base64 -w0)" >> $GITHUB_OUTPUT
- uses: actions/upload-artifacts@...
with:
path: ./distThe hash values are stored in the hashes output variable for later use in the provenance job.
Generate provenance
The provenance job consumes the hash output via the needs relationship and invokes the reusable SLSA builder workflow to create a signed provenance file ( .intoto.jsonl ).
jobs:
provenance:
needs: [build]
uses: slsa-framework/slsa-github-builder/.github/workflows/[email protected]
permissions:
actions: read
id-token: write
contents: write
with:
subject-base64: ${{ provenance.needs.build.output.hashes }}
upload-assets: trueThe resulting JSON‑Lines file contains the cryptographic proof that the artifacts were produced by the declared workflow.
Upload to PyPI
The official pypa/gh-action-pypi-publish action uploads the wheel to PyPI after the build and provenance steps have succeeded:
publish:
needs: ["build", "provenance"]
permissions:
contents: write
runs-on: "ubuntu-latest"
steps:
- uses: actions/download-artifact@...
with:
name: "dist"
path: "dist/"
- env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
run: gh release upload ${{ github.ref_name }} dist/* --repo ${{ github.repository }}
- uses: "pypa/gh-action-pypi-publish@..."
with:
user: __token__
password: ${{ secrets.PYPI_TOKEN }}Verify the package provenance
To verify a published package, the article uses the slsa‑verifier tool. As an example, it downloads the urllib3 wheel (version 2.1.0) and the matching provenance file from the GitHub release, then runs:
# download wheel
python3 -m pip download --only-binary=:all: urllib3
# download provenance
curl --location -O https://github.com/urllib3/urllib3/releases/download/2.1.0/multiple.intoto.jsonl
# verify
slsa-verifier verify-artifact --provenance-path multiple.intoto.jsonl --source-uri github.com/urllib3/urllib3 urllib3-2.1.0-py3-none-any.whlThe verifier confirms the builder ID, source repository, tag, and commit, proving that the wheel was built exactly as described. After a successful verification, the wheel can be safely installed.
Projects and tools referenced
SLSA GitHub Builder
slsa‑framework/slsa‑verifier
pypa/gh-action-pypi-publish
pypa/build
urllib3/urllib3
DevOps Engineer
DevOps engineer, Pythonista and FOSS contributor. Created cpp-linter, commit-check, etc.; contributed to PyPA.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.