From ef5834354e7397e15ba3f93d1ce768d8ec3f421f Mon Sep 17 00:00:00 2001 From: Ben Date: Sat, 16 May 2026 21:48:17 +0000 Subject: [PATCH] Add SLSA Source Provenance Attestation via slsa-source-corroborator Closes gap C-037. Publishes a Source Provenance Attestation (predicate type https://slsa.dev/source_provenance/v1) on every push to main via slsa-framework/slsa-source-corroborator, proving that branch protection, mandatory code review, and ancestry enforcement (C-038) were applied to each commit. --- .github/workflows/source-provenance.yml | 31 ++++++++++ doc/explanation/threat_model_supply_chain.rst | 16 ++--- doc/howto/verify-integrity.rst | 39 +++++++++--- security/tm_supply_chain.py | 59 +++++++++---------- 4 files changed, 97 insertions(+), 48 deletions(-) diff --git a/.github/workflows/source-provenance.yml b/.github/workflows/source-provenance.yml index 8273bbcd..c7bbfb5e 100644 --- a/.github/workflows/source-provenance.yml +++ b/.github/workflows/source-provenance.yml @@ -10,6 +10,37 @@ permissions: contents: read jobs: + attest-source-governance: + name: Attest source governance (SLSA Source Track) + runs-on: ubuntu-latest + if: github.ref == 'refs/heads/main' + permissions: + contents: read + attestations: write + id-token: write + + steps: + - name: "Harden the runner (Block egress traffic: Only allow calls to allowed endpoints)" + uses: step-security/harden-runner@ab7a9404c0f3da075243ca237b5fac12c98deaa5 # v2.19.3 + with: + egress-policy: block + allowed-endpoints: >+ + github.com:443 + api.github.com:443 + uploads.github.com:443 + fulcio.sigstore.dev:443 + rekor.sigstore.dev:443 + tuf-repo-cdn.sigstore.dev:443 + *.blob.core.windows.net:443 + + - name: Checkout repository + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Attest source governance (SLSA Source Track) + uses: slsa-framework/slsa-source-corroborator@v0.1.0 + attest-source: name: Generate source provenance runs-on: ubuntu-latest diff --git a/doc/explanation/threat_model_supply_chain.rst b/doc/explanation/threat_model_supply_chain.rst index 2629b25d..dad2e28d 100644 --- a/doc/explanation/threat_model_supply_chain.rst +++ b/doc/explanation/threat_model_supply_chain.rst @@ -131,7 +131,7 @@ Actors - External contributor submitting pull requests, or an adversary attempting supply-chain manipulation (malicious PR, action-poisoning, or MITM on CI network traffic). Code review, branch protection, and SHA-pinned Actions are the primary controls at this boundary. * - Consumer / End User - - Installs dfetch from PyPI (``pip install dfetch``) or from binary installer, then invokes it on a developer workstation or in a CI pipeline. Can verify four complementary attestation types using ``gh attestation verify`` as documented in the release-integrity guide (see C-026, C-039, C-040): SBOM attestation on the PyPI wheel; SBOM, SLSA build provenance, and VSA on binary installers; SLSA build provenance and in-toto test result attestation on the source archive. + - Installs dfetch from PyPI (``pip install dfetch``) or from binary installer, then invokes it on a developer workstation or in a CI pipeline. Can verify five complementary attestation types using ``gh attestation verify`` as documented in the release-integrity guide (see C-026, C-037, C-039, C-040): SBOM attestation on the PyPI wheel; SBOM, SLSA build provenance, and VSA on binary installers; SLSA build provenance, in-toto test result attestation, and SLSA Source Provenance Attestation on the source archive and main-branch commits. Boundaries @@ -319,6 +319,12 @@ Controls - Tampering - DFT-28 - Mitigates: ccache and clcache keys in ``build.yml`` include ``${{ github.ref_name }}`` so cache entries written by a pull-request build are scoped to the PR's branch name and cannot be restored by a release-tag build. A malicious fork PR step cannot pre-populate a cache slot that the release workflow will restore, because the release tag name is not reachable from the PR's branch ref. ``.github/workflows/build.yml`` + * - C-037 + - SLSA Source Provenance Attestation of repository governance controls + - Low + - Repudiation, Spoofing + - DFT-31 + - Mitigates: Source Provenance Attestations are published via ``slsa-framework/slsa-source-corroborator`` on every push to ``main``. These attestations prove the specific source-level governance controls applied on each commit: branch protection, mandatory code review, and ancestry enforcement (C-038). Predicate type ``https://slsa.dev/source_provenance/v1`` is signed by GitHub Actions via Sigstore and stored in the GitHub Attestation registry. Consumers can verify using ``gh attestation verify`` with ``--predicate-type https://slsa.dev/source_provenance/v1`` and ``--cert-identity`` pinned to ``source-provenance.yml@refs/heads/main``. ``.github/workflows/source-provenance.yml`` * - C-038 - Ancestry enforcement on dfetch main branch - Low @@ -330,7 +336,7 @@ Controls - High - Spoofing, Tampering, Repudiation - DFT-31, DFT-25 - - Mitigates: Every dfetch release ships two complementary Sigstore-signed attestations that together let consumers trace the full source → binary chain. SLSA build provenance (``source-provenance.yml``) on the source archive proves the archive was produced from the official tagged commit by the official CI workflow, recording the exact inputs used at build time. A Verification Summary Attestation (VSA, ``build.yml``) on binary installers records that the source archive was itself attested and verified before the binary was produced, linking source-level trust to the installed package. Both are signed by GitHub Actions via Sigstore and can be verified using ``gh attestation verify`` with ``--predicate-type https://slsa.dev/provenance/v1`` or ``--predicate-type https://slsa.dev/verification_summary/v1`` respectively. This substantially mitigates DFT-31 (consumers now have attestations to verify against) and DFT-25 (forged provenance would fail Sigstore verification). The remaining gap (no formal SLSA Source Level attestation of governance controls) is tracked in C-037. ``doc/howto/verify-integrity.rst`` + - Mitigates: Every dfetch release ships two complementary Sigstore-signed attestations that together let consumers trace the full source → binary chain. SLSA build provenance (``source-provenance.yml``) on the source archive proves the archive was produced from the official tagged commit by the official CI workflow, recording the exact inputs used at build time. A Verification Summary Attestation (VSA, ``build.yml``) on binary installers records that the source archive was itself attested and verified before the binary was produced, linking source-level trust to the installed package. Both are signed by GitHub Actions via Sigstore and can be verified using ``gh attestation verify`` with ``--predicate-type https://slsa.dev/provenance/v1`` or ``--predicate-type https://slsa.dev/verification_summary/v1`` respectively. This substantially mitigates DFT-31 (consumers now have attestations to verify against) and DFT-25 (forged provenance would fail Sigstore verification). The formal SLSA Source Level attestation of governance controls is addressed by C-037. ``doc/howto/verify-integrity.rst`` * - C-040 - Test result attestation on source archive - Medium @@ -358,12 +364,6 @@ Gaps - Tampering - DFT-10 - Affects: ``pip install .`` and ``pip install --upgrade pip build`` in CI do not use ``--require-hashes``. A compromised PyPI mirror can substitute malicious build tooling. - * - C-037 - - No formal SLSA Source Level attestation of repository governance controls - - Low - - Repudiation, Spoofing - - DFT-31 - - Affects: dfetch now publishes SLSA build provenance for source archives, VSAs for binary installers (C-039), and in-toto test result attestations (C-040). These close the 'no attestation to verify against' concern: consumers can cryptographically verify the artifact chain. The remaining, narrower gap is that dfetch does not publish formal SLSA Source Provenance Attestations generated by a SLSA Source Generator — attestations that would prove the specific source-level governance controls applied on each commit (branch protection, mandatory code review, ancestry enforcement). C-038 establishes that ancestry enforcement is in place and C-026 documents what consumers can verify; the gap is in machine-readable, verifiable proof of those governance controls rather than the controls themselves. Risk is Low: the missing piece is a formal SLSA Source Level certificate (per the SLSA Source Track spec) rather than the absence of any assurance. Fix: publish Source Provenance Attestations via ``slsa-framework/slsa-source-corroborator`` or equivalent on each push to main. * - C-025 - No hardware-token MFA for release operations - High diff --git a/doc/howto/verify-integrity.rst b/doc/howto/verify-integrity.rst index 07408995..c159681e 100644 --- a/doc/howto/verify-integrity.rst +++ b/doc/howto/verify-integrity.rst @@ -5,20 +5,23 @@ Every dfetch release, and every commit merged to ``main``, has cryptographic attestations signed by GitHub Actions and anchored in `Sigstore `_, all published in the `attestation registry `_. -There are four complementary kinds: +There are five complementary kinds, in pipeline order from source to artifact: +- **Source Provenance Attestation** (SLSA Source Track) — answers *"did this commit + meet governance requirements?"*: proves that branch protection, mandatory code + review, and ancestry enforcement were in place when the commit was merged to + ``main``. +- **Test result attestation** (in-toto) — answers *"did the test suite pass?"*: + records that the full CI test suite ran against this exact source archive and every + check passed, before any binary was produced. - **SLSA build provenance** — answers *"where did this come from?"*: proves the artifact was produced from the official source commit by the official CI workflow, and records the exact inputs used at build time. - **SBOM attestation** (CycloneDX) — answers *"what is inside it?"*: lists every dependency bundled in the package so you can audit its composition. -- **Verification Summary Attestation (VSA)** — answers *"was the source - independently verified?"*: records that the source archive for this commit was - attested and verified before the binary was produced, linking source-level - trust to the binary package. -- **Test result attestation** (in-toto) — answers *"did the source pass its tests?"*: - records that the full CI test suite ran against this exact source archive and every - check passed, before any binary was produced. +- **Verification Summary Attestation (VSA)** — answers *"was the full chain + verified?"*: records that the source archive was itself attested and verified + before the binary was produced, linking source-level trust to the artifact. Binary installers have **build provenance, SBOM, and VSA** attestations when source provenance verification passes (signed by ``build.yml``). @@ -26,6 +29,8 @@ Python packages installed from PyPI have an **SBOM attestation only** (signed by ``python-publish.yml``). The source archive has a **SLSA build provenance** attestation (signed by ``source-provenance.yml``) and a **test result attestation** (signed by ``test.yml``). +Every commit merged to ``main`` has a **SLSA Source Provenance Attestation** proving +branch governance controls were applied (signed by ``source-provenance.yml``). To verify, use the `GitHub CLI `_. Pass ``--predicate-type`` to target one kind specifically; omit it to accept any. @@ -201,6 +206,24 @@ any binary was produced): --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/test.yml@refs/tags/v \ --cert-oidc-issuer https://token.actions.githubusercontent.com +**Source governance — verify SLSA Source Provenance Attestation:** + +Every commit merged to ``main`` has a Source Provenance Attestation proving that +branch protection, mandatory code review, and ancestry enforcement were in place +when the commit landed. These attestations are published by +``slsa-framework/slsa-source-corroborator`` and stored in the +`attestation registry `_. +Replace ```` with the 40-character commit SHA you want to verify: + +.. code-block:: bash + + $ gh attestation verify \ + "git+https://github.com/dfetch-org/dfetch@" \ + --repo dfetch-org/dfetch \ + --predicate-type https://slsa.dev/source_provenance/v1 \ + --cert-identity https://github.com/dfetch-org/dfetch/.github/workflows/source-provenance.yml@refs/heads/main \ + --cert-oidc-issuer https://token.actions.githubusercontent.com + See `GitHub artifact attestations`_ for details. .. note:: diff --git a/security/tm_supply_chain.py b/security/tm_supply_chain.py index c7493875..7ea7a9ba 100644 --- a/security/tm_supply_chain.py +++ b/security/tm_supply_chain.py @@ -98,10 +98,11 @@ def _make_sc_actors_and_entities( consumer.description = ( "Installs dfetch from PyPI (``pip install dfetch``) or from binary installer, " "then invokes it on a developer workstation or in a CI pipeline. " - "Can verify four complementary attestation types using ``gh attestation verify`` as " - "documented in the release-integrity guide (see C-026, C-039, C-040): " + "Can verify five complementary attestation types using ``gh attestation verify`` as " + "documented in the release-integrity guide (see C-026, C-037, C-039, C-040): " "SBOM attestation on the PyPI wheel; SBOM, SLSA build provenance, and VSA on binary " - "installers; SLSA build provenance and in-toto test result attestation on the source archive." + "installers; SLSA build provenance, in-toto test result attestation, and SLSA Source " + "Provenance Attestation on the source archive and main-branch commits." ) gh_repository = ExternalEntity("A-01: GitHub Repository") gh_repository.inBoundary = b_github @@ -658,6 +659,28 @@ def build_model() -> TM: "the PR's branch ref." ), ), + Control( + id="C-037", + name="SLSA Source Provenance Attestation of repository governance controls", + assets=["A-01"], + threats=["DFT-31"], + reference=".github/workflows/source-provenance.yml", + assessment=ControlAssessment( + status="implemented", risk="Low", stride=["Repudiation", "Spoofing"] + ), + description=( + "Source Provenance Attestations are published via " + "``slsa-framework/slsa-source-corroborator`` on every push to ``main``. " + "These attestations prove the specific source-level governance controls " + "applied on each commit: branch protection, mandatory code review, and " + "ancestry enforcement (C-038). " + "Predicate type ``https://slsa.dev/source_provenance/v1`` is signed by " + "GitHub Actions via Sigstore and stored in the GitHub Attestation registry. " + "Consumers can verify using ``gh attestation verify`` with " + "``--predicate-type https://slsa.dev/source_provenance/v1`` and " + "``--cert-identity`` pinned to ``source-provenance.yml@refs/heads/main``." + ), + ), Control( id="C-038", name="Ancestry enforcement on dfetch main branch", @@ -701,8 +724,7 @@ def build_model() -> TM: "or ``--predicate-type https://slsa.dev/verification_summary/v1`` respectively. " "This substantially mitigates DFT-31 (consumers now have attestations to verify " "against) and DFT-25 (forged provenance would fail Sigstore verification). " - "The remaining gap (no formal SLSA Source Level attestation of governance " - "controls) is tracked in C-037." + "The formal SLSA Source Level attestation of governance controls is addressed by C-037." ), ), Control( @@ -742,33 +764,6 @@ def build_model() -> TM: "malicious build tooling." ), ), - Control( - id="C-037", - name="No formal SLSA Source Level attestation of repository governance controls", - assets=["A-01"], - threats=["DFT-31"], - assessment=ControlAssessment( - status="gap", risk="Low", stride=["Repudiation", "Spoofing"] - ), - description=( - "dfetch now publishes SLSA build provenance for source archives, VSAs for " - "binary installers (C-039), and in-toto test result attestations (C-040). " - "These close the 'no attestation to verify against' concern: consumers can " - "cryptographically verify the artifact chain. " - "The remaining, narrower gap is that dfetch does not publish formal SLSA " - "Source Provenance Attestations generated by a SLSA Source Generator — " - "attestations that would prove the specific source-level governance controls " - "applied on each commit (branch protection, mandatory code review, ancestry " - "enforcement). " - "C-038 establishes that ancestry enforcement is in place and C-026 documents " - "what consumers can verify; the gap is in machine-readable, verifiable " - "proof of those governance controls rather than the controls themselves. " - "Risk is Low: the missing piece is a formal SLSA Source Level certificate " - "(per the SLSA Source Track spec) rather than the absence of any assurance. " - "Fix: publish Source Provenance Attestations via " - "``slsa-framework/slsa-source-corroborator`` or equivalent on each push to main." - ), - ), Control( id="C-025", name="No hardware-token MFA for release operations",