Problem
The existing release.yml creates GitHub Releases on tags but does not:
- Sign release artifacts (GPG or Sigstore)
- Generate SLSA provenance for the Windows EXE and Python wheel
- Enforce branch protection preventing direct pushes to
main
These gaps leave the supply chain open and suppress Scorecard Signed-Releases and Branch-Protection scores.
Part 1: Branch Protection (Manual + Automated)
Manual Settings (GitHub UI: Settings > Branches > Add Rule for main)
Automation: .github/workflows/branch-protection.yml (Scorecard validation)
name: Branch Protection Check
on:
push:
branches: [main]
jobs:
verify:
runs-on: ubuntu-latest
steps:
- name: Verify no direct push to main
run: |
if [ "${{ github.event_name }}" = "push" ]; then
echo "Direct push to main detected! This should be blocked by branch protection."
echo "If this CI run is seeing this, branch protection may not be configured correctly."
fi
Part 2: Signed Releases via Sigstore
Update release.yml to add artifact signing:
name: Release
on:
push:
tags:
- 'v*.*.*'
permissions:
contents: write
id-token: write # Required for Sigstore OIDC signing
jobs:
build-and-sign:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- uses: actions/setup-python@0b93645e9fea7318ecaed2b359559ac225c90a2b # v5.3.0
with:
python-version: "3.11"
- name: Build wheel and sdist
run: |
pip install build
python -m build
- name: Sign artifacts with Sigstore
uses: sigstore/gh-action-sigstore-python@f514d46b907ebcd5bedc05145c03b69c1edd8b46 # v3.0.0
with:
inputs: |
dist/*.whl
dist/*.tar.gz
- name: Create GitHub Release
uses: softprops/action-gh-release@e7a8f85e1c67a31e6ed99a94b41bd0b71bbee6b9 # v2.0.6
with:
files: |
dist/*.whl
dist/*.tar.gz
dist/*.sigstore.json
generate_release_notes: true
fail_on_unmatched_files: true
Part 3: SLSA Provenance for Python Package
Add SLSA Level 3 provenance generation:
slsa-provenance:
needs: [build-and-sign]
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
base64-subjects: "${{ needs.build-and-sign.outputs.hashes }}"
upload-assets: true
upload-tag-name: ${{ github.ref_name }}
Part 4: SLSA Provenance for Windows EXE (build-exe.yml)
Part 5: Signed Git Tags
Acceptance Criteria
Scorecard Impact
| Check |
Before |
After |
| Signed-Releases |
0/10 |
10/10 |
| Branch-Protection |
~3/10 |
10/10 |
| Code-Review |
~3/10 |
8-10/10 |
Dependencies
Requires: #10 (CI must be green for required status checks), #11 (SHA pinning), #12 (CodeQL as required check)
Estimated effort: 4-6 hours
Priority: P1
Labels: security, releases, supply-chain, branch-protection, scorecard, P1
Problem
The existing
release.ymlcreates GitHub Releases on tags but does not:mainThese gaps leave the supply chain open and suppress Scorecard
Signed-ReleasesandBranch-Protectionscores.Part 1: Branch Protection (Manual + Automated)
Manual Settings (GitHub UI: Settings > Branches > Add Rule for
main)lint,test (3.11),test (3.12),build,smoke,analyze (python)(CodeQL)Automation:
.github/workflows/branch-protection.yml(Scorecard validation)Part 2: Signed Releases via Sigstore
Update
release.ymlto add artifact signing:Part 3: SLSA Provenance for Python Package
Add SLSA Level 3 provenance generation:
Part 4: SLSA Provenance for Windows EXE (
build-exe.yml)build-exe.ymlto output SHA256 hash of generated.exePart 5: Signed Git Tags
git config --global user.signingkey YOUR_KEY_ID git config --global tag.gpgSign trueSECURITY.mdfor verificationgh attestation signwith Sigstore (no GPG key needed)Acceptance Criteria
.whland.tar.gzwith Sigstore.sigstore.jsonfiles attached to every GitHub ReleaseSigned-Releasescheck shows 10/10Branch-Protectioncheck shows 10/10Scorecard Impact
Dependencies
Requires: #10 (CI must be green for required status checks), #11 (SHA pinning), #12 (CodeQL as required check)
Estimated effort: 4-6 hours
Priority: P1
Labels: security, releases, supply-chain, branch-protection, scorecard, P1