Chapter 23Lesson 05~225 minutes

Checkpoint Lab — Artifact Attestations, SBOM Provenance, Verification, and SLSA-Oriented Builds

Produce and verify an attested tiny artifact plus SBOM, prove tampering and wrong-identity checks fail, and document residual supply-chain risk.

CheckpointAttestationSBOMNegative verificationEvidence packet

Learning objectives

  • Complete an end-to-end release-evidence checkpoint without production credentials or infrastructure.
  • Predict subject, permission, attestation and verification state changes before execution.
  • Generate provenance and SBOM attestations for one exact artifact and retain immutable action references.
  • Demonstrate both tamper rejection and wrong-builder rejection while preserving first-failure evidence.
  • Write a residual-risk statement that distinguishes provenance confidence from software-security confidence.

1. Checkpoint mission

You are preparing a tiny command-line asset for release from a disposable repository. Your gate requires two pieces of signed evidence: build provenance and an SPDX SBOM attestation. A consumer must verify the exact artifact against the intended repository and signer workflow before promotion. You will then prove that both byte tampering and wrong-signer policy are rejected.

No production package, registry, cloud environment or deployment is required. The live path uses a public disposable repository and GitHub-hosted runner. The fallback is an explicitly unsigned local simulation.

2. Preflight contract

Check Required state
Repo Disposable public gha-provenance-checkpoint; no proprietary source/data.
Branch Use the repository default branch for the checkpoint; record exact ref/SHA.
Runner ubuntu-24.04.
Actions Checkout v7.0.1 @ 3d3c42e5aac5ba805825da76410c181273ba90b1; Attest v4.2.2 @ 1e69f48acb82d1966a394da916b4c1698aa569d6.
Permissions contents: read, id-token: write, attestations: write, artifact-metadata: write; nothing broader.
Credentials No repository/cloud secrets. GitHub-provided token/OIDC only.
CLI Authenticated gh for your disposable account; record actual version.

3. Predict state before running

Write your predictions before dispatching. At minimum predict these four changes: (1) a new workflow run/attempt will exist for one immutable source SHA; (2) the runner will create a new artifact digest from that source; (3) two attestation records with different predicate types will be associated with that digest; and (4) no deployment/package/release target will change.

Also predict two negative outcomes: changing one byte should break subject verification, and requiring an unrelated signer workflow should reject the original bytes.

4. Create the checkpoint source

Create the following source and build script. The artifact embeds the source SHA at build time so the evidence packet can correlate visible content with run metadata, while SHA-256 remains the authoritative byte identity.

mkdir -p src scripts .github/workflows
printf 'academy-provenance-checkpoint
' > src/app.txt
cat > scripts/build.sh <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
rm -rf dist
mkdir -p dist
{
  cat src/app.txt
  printf 'source_sha=%s
' "${GITHUB_SHA:-local}"
} > dist/checkpoint.txt
sha256sum dist/checkpoint.txt > dist/checkpoint.txt.sha256
EOF
chmod +x scripts/build.sh

5. Exact workflow

This workflow builds, creates a standards-based toy SBOM and signs both provenance and SBOM claims. It records only non-secret outputs and never prints a raw OIDC token.

name: Provenance checkpoint
on:
  workflow_dispatch:
permissions:
  contents: read
  id-token: write
  attestations: write
  artifact-metadata: write
jobs:
  release-evidence:
    runs-on: ubuntu-24.04
    steps:
      - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
        with:
          persist-credentials: false

      - name: Build exact subject
        run: |
          ./scripts/build.sh
          cat dist/checkpoint.txt.sha256
          printf 'run=%s attempt=%s sha=%s ref=%s\n' \
            "$GITHUB_RUN_ID" "$GITHUB_RUN_ATTEMPT" "$GITHUB_SHA" "$GITHUB_REF"
          python3 --version
          gh --version | head -1

      - name: Create SPDX 2.3 SBOM
        run: |
          python3 - <<'PY'
          import hashlib, json, os, datetime
          p='dist/checkpoint.txt'
          d=hashlib.sha256(open(p,'rb').read()).hexdigest()
          sbom={
            'spdxVersion':'SPDX-2.3','dataLicense':'CC0-1.0',
            'SPDXID':'SPDXRef-DOCUMENT','name':'gha-provenance-checkpoint',
            'documentNamespace':f'https://example.invalid/spdx/{os.environ["GITHUB_SHA"]}',
            'creationInfo':{'created':datetime.datetime.now(datetime.timezone.utc).strftime('%Y-%m-%dT%H:%M:%SZ'),
                            'creators':['Tool: gha-course-checkpoint']},
            'files':[{'fileName':'dist/checkpoint.txt','SPDXID':'SPDXRef-File-checkpoint',
                      'checksums':[{'algorithm':'SHA256','checksumValue':d}],
                      'licenseConcluded':'NOASSERTION','copyrightText':'NOASSERTION'}]
          }
          open('dist/checkpoint.spdx.json','w').write(json.dumps(sbom,indent=2)+'\n')
          PY

      - name: Sign provenance claim
        id: prov
        uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
        with:
          subject-path: dist/checkpoint.txt

      - name: Sign SBOM claim
        id: sbom
        uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
        with:
          subject-path: dist/checkpoint.txt
          sbom-path: dist/checkpoint.spdx.json

      - name: Evidence summary
        env:
          PROV_ID: ${{ steps.prov.outputs.attestation-id }}
          PROV_URL: ${{ steps.prov.outputs.attestation-url }}
          SBOM_ID: ${{ steps.sbom.outputs.attestation-id }}
          SBOM_URL: ${{ steps.sbom.outputs.attestation-url }}
        run: |
          {
            echo '## Checkpoint evidence'
            echo "- run: $GITHUB_RUN_ID / attempt $GITHUB_RUN_ATTEMPT"
            echo "- source: $GITHUB_SHA"
            echo "- provenance attestation: $PROV_ID"
            echo "- provenance URL: $PROV_URL"
            echo "- SBOM attestation: $SBOM_ID"
            echo "- SBOM URL: $SBOM_URL"
          } >> "$GITHUB_STEP_SUMMARY"

6. Dispatch and preserve Run A

Commit the files, push to the disposable public repository and dispatch Run A. Do not rerun immediately if a step fails. First save the failure output, run ID, attempt, SHA, runner image metadata and action references. If the action fails because the repository is private or the feature is unavailable, document that prerequisite mismatch and use the simulation path rather than weakening permissions.

7. Recreate the exact artifact locally

Check out Run A's exact source SHA locally, export GITHUB_SHA to the same SHA and run the deterministic build. Compare its checksum with Run A. If they differ, stop: you do not yet have the same subject and should not attempt verification.

git checkout RUN_A_SOURCE_SHA
export GITHUB_SHA=RUN_A_SOURCE_SHA
./scripts/build.sh
sha256sum dist/checkpoint.txt
cat dist/checkpoint.txt.sha256

8. Positive verification: bytes + expected builder identity

Record the CLI version, then verify the original subject against the expected repository and exact signer workflow path.

gh --version
gh attestation verify dist/checkpoint.txt   --repo YOUR_OWNER/gha-provenance-checkpoint   --signer-workflow YOUR_OWNER/gha-provenance-checkpoint/.github/workflows/provenance-checkpoint.yml

Save the complete verifier output as verify-provenance-success.txt.

9. Verify the SBOM predicate separately

gh attestation verify dist/checkpoint.txt   --repo YOUR_OWNER/gha-provenance-checkpoint   --predicate-type https://spdx.dev/Document/v2.3

Record that this verifies the signed inventory claim for the subject; it does not certify vulnerability-free software.

10. Negative case 1 — tampered bytes

Preserve the valid file, create a tampered copy and capture the expected rejection without deleting the valid subject.

cp dist/checkpoint.txt dist/checkpoint.valid.txt
cp dist/checkpoint.txt dist/checkpoint.tampered.txt
printf 'tamper
' >> dist/checkpoint.tampered.txt
sha256sum dist/checkpoint.valid.txt dist/checkpoint.tampered.txt

gh attestation verify dist/checkpoint.tampered.txt   --repo YOUR_OWNER/gha-provenance-checkpoint   > verify-tamper.txt 2>&1 || true
cat verify-tamper.txt

11. Negative case 2 — wrong signer policy

Use the untouched valid subject but require a nonexistent/unapproved workflow identity.

gh attestation verify dist/checkpoint.valid.txt   --repo YOUR_OWNER/gha-provenance-checkpoint   --signer-workflow YOUR_OWNER/gha-provenance-checkpoint/.github/workflows/unapproved.yml   > verify-wrong-signer.txt 2>&1 || true
cat verify-wrong-signer.txt

12. Required evidence packet

Category Required evidence
Source/event workflow_dispatch, repository, ref, source SHA.
Run/job Run ID, attempt, job/step conclusions.
Runner/tool Hosted image metadata, Python version, actual gh CLI version.
Dependencies Checkout/attest exact SHAs and mapped releases.
Permissions Exact job/workflow permission block.
Subject Original SHA-256 and filename.
Provenance Attestation ID/URL + successful identity-constrained verification.
SBOM SPDX JSON + attestation ID/URL + predicate verification.
Negative tests Tampered digest/failure + wrong-signer failure.
External state Statement that no package/release/deployment/cloud target changed.
Assumptions Public disposable repo, current GitHub.com service, toy artifact/SBOM.

13. Residual supply-chain risk statement

Finish with a short written risk statement. It must say that successful verification confirms the artifact digest is covered by attestations from the expected GitHub Actions identity under the tested policy. It must also state what remains unproven: source correctness, dependency safety, test adequacy, absence of vulnerabilities, runner/service compromise beyond the attestation model, SBOM completeness, and deployment/runtime health.

14. Faithful free/local fallback

If live artifact attestations cannot be used, run the same deterministic build and SBOM generation, then create an UNSIGNED SIMULATION provenance JSON with repository/workflow/ref/digest and test a local policy checker against valid, tampered and wrong-identity cases. Keep the language explicit: this demonstrates subject/policy behavior but does not create a GitHub/Sigstore signature or attestation ID.

15. Cleanup / rollback

After grading your evidence packet, delete the disposable repository if desired. If you experiment with attestation lifecycle deletion APIs, that is a separate destructive exercise: preserve the attestation IDs/bundles and first-failure evidence, guard the exact disposable repository and subject digest, and never delete evidence from production systems as routine troubleshooting.

16. Production contract and bridge to Chapter 24

Chapter 23 adds a release-integrity contract: exact source + immutable workflow dependencies → exact build bytes → digest-bound signed provenance/SBOM → identity-constrained consumer verification → policy decision. Chapter 24 now uses that disciplined evidence model to compose fast CI stages for build, test, lint and coverage without collapsing all quality signals into one opaque job.

Next lesson

Continuous Integration Patterns for Build, Test, Lint, and Coverage: Core Concepts and Mental Model

Continue with the next lesson to build on the current concepts, evidence, security boundaries, and operational practices.

Knowledge check

What two negative tests are required by this checkpoint?

Why does the checkpoint not publish a release package?

A valid provenance and SBOM both verify. Can the team claim the artifact has no vulnerabilities?

Why record the exact actions/attest commit SHA?

What should happen before a consumer promotes the artifact?

Official references and version notes

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.