Checkpoint Lab — Artifact Attestations, SBOMs, Build Provenance, Verification, and SLSA-Oriented Workflows
The checkpoint produces one deterministic artifact, provenance attestation and SPDX SBOM attestation, verifies repository/workflow/ref identity, deliberately mutates the artifact to force verification failure, compares provenance with inventory evidence, and writes a deployment gate that treats attestations as evidence—not as a blanket claim that software is secure.
Learning objectives
- Produce and identify a deterministic artifact by digest, then generate and verify real GitHub provenance and SBOM attestations in a public disposable repository.
- Predict repository/workflow/evidence state before the run and independently verify run SHA, artifact digest, subject and signer/ref policy afterward.
- Mutate a copy of the artifact to force a verification failure and preserve the original evidence while diagnosing the cause.
- Compare provenance claims with SBOM inventory and write a deployment policy that requires both where appropriate.
- Define a versioned, reviewable SLSA-oriented operating statement without overstating certification or application security.
gh attestation, and no
paid cloud/registry. The lab creates cryptographic attestations
through GitHub’s OIDC/Sigstore service but creates no signing key or
long-lived credential.
1. Scenario and predictions before changing state
Atlas Relay is preparing a deployment gate. The team no longer accepts “download from the release page” as provenance. It wants exact bytes, authenticated build identity, component inventory, and a controlled response when any of those differ.
| Prediction | Expected result | Independent verification |
|---|---|---|
| P1: workflow commit and artifact digest are different identities |
Run headSha is a Git SHA; tar has SHA-256 bytes
digest
|
Compare gh run view with
sha256sum.
|
| P2: one artifact can have more than one typed attestation | Same tar digest has SLSA provenance + SPDX predicate |
Run two gh attestation verify commands with
different predicate types.
|
| P3: changing one byte invalidates the original subject match | Tampered copy gets a new digest and fails verification | Preserve verifier stderr/exit code. |
| P4: a broad owner-only trust policy can be weaker than exact workflow/ref policy | Both may cryptographically verify, but exact policy has narrower authorization | Compare verifier constraints and document acceptance rule. |
Record P1–P4 in CHECKPOINT.md before running the
workflow. The checkpoint is an experiment: prediction first, state
change second, independent evidence third.
2. Setup and preflight
gh --version
gh auth status
gh attestation verify --help | sed -n '1,100p'
OWNER=$(gh api -H "X-GitHub-Api-Version: 2026-03-10" user --jq .login)
REPO="$OWNER/gh-attestation-ch25-checkpoint"
gh repo create "$REPO" --public --add-readme \
--description "Disposable Chapter 25 checkpoint"
gh repo clone "$REPO" ch25-checkpoint
cd ch25-checkpoint
DEFAULT_BRANCH=$(gh repo view "$REPO" --json defaultBranchRef --jq .defaultBranchRef.name)
BASE_SHA=$(git rev-parse HEAD)
printf 'repo=%s branch=%s base_sha=%s\n' "$REPO" "$DEFAULT_BRANCH" "$BASE_SHA"
Inspect the repository UI and confirm it is the disposable public repository. If it is private, stop: the free path intentionally depends on public-repository attestation availability.
3. Install the governed workflow
Use the exact workflow from Lesson 2 as
.github/workflows/ch25-attest.yml. Its permissions are
deliberately limited to reading source, requesting OIDC identity and
writing attestations. It does not publish a package, mutate Issues,
write repository contents, or receive secrets.
name: Chapter 25 provenance lab
on:
workflow_dispatch:
permissions:
contents: read
id-token: write
attestations: write
jobs:
build-attest:
runs-on: ubuntu-24.04
steps:
- name: Check out exact source
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Build deterministic artifact and SPDX SBOM
shell: bash
run: |
set -euo pipefail
rm -rf build dist
mkdir -p build/atlas-lab dist
printf 'atlas-provenance-lab\nversion=1\n' > build/atlas-lab/README.txt
printf 'runtime-dependencies=none\n' > build/atlas-lab/components.txt
tar --sort=name \
--mtime='UTC 1970-01-01' \
--owner=0 --group=0 --numeric-owner \
-C build -cf dist/atlas-lab.tar atlas-lab
(cd dist && sha256sum atlas-lab.tar > SHA256SUMS)
ARTIFACT_SHA=$(cut -d' ' -f1 dist/SHA256SUMS)
export ARTIFACT_SHA
python - <<'PY'
import json, os
sha = os.environ['ARTIFACT_SHA']
doc = {
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "atlas-lab-sbom",
"documentNamespace": "https://example.invalid/spdx/atlas-lab/" + sha,
"creationInfo": {
"created": "1970-01-01T00:00:00Z",
"creators": ["Tool: chapter-25-lab-generator/1.0"]
},
"packages": [{
"name": "atlas-lab",
"SPDXID": "SPDXRef-Package-atlas-lab",
"versionInfo": "1.0.0",
"downloadLocation": "NOASSERTION",
"filesAnalyzed": False,
"checksums": [{"algorithm": "SHA256", "checksumValue": sha}]
}],
"relationships": [{
"spdxElementId": "SPDXRef-DOCUMENT",
"relationshipType": "DESCRIBES",
"relatedSpdxElement": "SPDXRef-Package-atlas-lab"
}]
}
with open('dist/atlas-lab.spdx.json', 'w', encoding='utf-8') as f:
json.dump(doc, f, indent=2, sort_keys=True)
f.write('\n')
PY
cat dist/SHA256SUMS
- name: Attest build provenance
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-path: dist/atlas-lab.tar
- name: Attest SPDX SBOM
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4
with:
subject-path: dist/atlas-lab.tar
sbom-path: dist/atlas-lab.spdx.json
- name: Upload exact lab evidence
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
with:
name: ch25-evidence
path: |
dist/atlas-lab.tar
dist/SHA256SUMS
dist/atlas-lab.spdx.json
if-no-files-found: error
retention-days: 7
mkdir -p .github/workflows
# Save the YAML above to .github/workflows/ch25-attest.yml
cat > CHECKPOINT.md <<'EOF'
# Chapter 25 predictions
- P1: source Git SHA and artifact SHA-256 will differ.
- P2: the same artifact digest will have provenance and SPDX attestations.
- P3: a one-byte mutation will fail attestation verification.
- P4: production policy will require repository + signer workflow + source ref, not owner-only trust.
EOF
git add .github/workflows/ch25-attest.yml CHECKPOINT.md
git commit -m "Add Chapter 25 attestation checkpoint"
git push origin HEAD
CHECKPOINT_SOURCE_SHA=$(git rev-parse HEAD)
4. Build, attest and capture run identity
gh workflow run ch25-attest.yml -R "$REPO" --ref "$DEFAULT_BRANCH"
RUN_ID=$(gh run list -R "$REPO" --workflow ch25-attest.yml --limit 1 --json databaseId --jq '.[0].databaseId')
gh run watch "$RUN_ID" -R "$REPO" --exit-status
gh run view "$RUN_ID" -R "$REPO" --json databaseId,headSha,event,conclusion,createdAt,jobs --jq '{databaseId,headSha,event,conclusion,createdAt,jobs:[.jobs[]|{name,conclusion}]}'
Prediction P1 starts here: save headSha. It identifies
the source/workflow run, not the output bytes.
5. Download exact evidence and prove the original artifact
cd ..
rm -rf ch25-checkpoint-verify
mkdir ch25-checkpoint-verify
gh run download "$RUN_ID" -R "$REPO" -n ch25-evidence -D ch25-checkpoint-verify
cd ch25-checkpoint-verify
sha256sum -c SHA256SUMS
ARTIFACT_SHA=$(sha256sum atlas-lab.tar | cut -d' ' -f1)
printf 'artifact_sha256=%s
' "$ARTIFACT_SHA"
# Provenance gate: repository + exact signer workflow + source ref + hosted-runner policy.
gh attestation verify atlas-lab.tar -R "$REPO" --signer-workflow "$REPO/.github/workflows/ch25-attest.yml" --source-ref "refs/heads/$DEFAULT_BRANCH" --deny-self-hosted-runners --format json > provenance.json
jq '.[0].verificationResult.statement | {subject,predicateType}' provenance.json
P1 is confirmed when the Git run SHA and artifact SHA-256 are visibly different. The provenance subject should contain the artifact’s digest, while the certificate/verification result authenticates the GitHub Actions builder identity.
6. Prove the second predicate and compare it with provenance
gh attestation verify atlas-lab.tar -R "$REPO" --signer-workflow "$REPO/.github/workflows/ch25-attest.yml" --source-ref "refs/heads/$DEFAULT_BRANCH" --deny-self-hosted-runners --predicate-type https://spdx.dev/Document/v2.3 --format json > sbom-verification.json
jq '.[0].verificationResult.statement | {subject,predicateType}' sbom-verification.json
jq '.[0].verificationResult.statement.predicate | {spdxVersion,name,packages}' sbom-verification.json
P2 is confirmed when the same subject digest has two verified predicate types. Provenance tells the build story; SPDX tells the inventory story. The checkpoint’s component inventory is intentionally trivial; production SBOM generation must examine the final shipped object or otherwise prove resolved-component coverage.
7. Inject one-byte substitution and diagnose the failure
cp atlas-lab.tar atlas-lab-tampered.tar
printf 'X' >> atlas-lab-tampered.tar
sha256sum atlas-lab.tar atlas-lab-tampered.tar
set +e
gh attestation verify atlas-lab-tampered.tar -R "$REPO" --signer-workflow "$REPO/.github/workflows/ch25-attest.yml" --source-ref "refs/heads/$DEFAULT_BRANCH" --deny-self-hosted-runners 2>tamper.stderr
TAMPER_STATUS=$?
set -e
cat tamper.stderr
printf 'tamper_status=%s
' "$TAMPER_STATUS"
test "$TAMPER_STATUS" -ne 0
P3 is confirmed only if the tampered copy has a different digest and verification fails. Keep the original file and failure evidence. Do not “repair” the tampered copy by signing it; the correct response is to retrieve/rebuild the expected artifact and investigate the distribution change.
8. Cross-check attestation inventory through the versioned REST API
SUBJECT="sha256:$ARTIFACT_SHA"
gh api --paginate \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2026-03-10" \
"repos/$REPO/attestations/$SUBJECT?per_page=100" \
--jq '{subject:"'"'$SUBJECT'"'",attestation_count:(.attestations|length),initiators:[.attestations[].initiator]}'
Use this as independent metadata/inventory evidence. Do not replace
the two gh attestation verify checks with this
response; REST presence is not cryptographic acceptance.
9. Write the deployment gate
deployment_gate:
artifact_identity:
algorithm: sha256
immutable_digest_required: true
attestations:
provenance:
predicate: https://slsa.dev/provenance/v1
required: true
sbom:
predicate: https://spdx.dev/Document/v2.3
required: true
authenticated_builder:
repository: "OWNER/gh-attestation-ch25-checkpoint"
signer_workflow: ".github/workflows/ch25-attest.yml"
source_refs:
- "refs/heads/main" # replace with the actual protected release ref policy
self_hosted_runners: deny
consumer_behavior:
verify_before_deploy: true
fail_closed_on_digest_or_identity_mismatch: true
exceptions:
approver: "supply-chain-owner"
evidence_required: true
expiry_required: true
P4 is now a production rule: the gate does not trust every attestation from the owner. It requires the expected repository, signer workflow and source-ref policy. Adapt the branch to the repository’s actual protected release model; for high-value releases, an exact source digest or governed release tag may be better than a broad branch constraint.
10. Write the SLSA-oriented statement without overclaiming
Record: “This checkpoint generates GitHub artifact attestations and verifies them under an explicit consumer policy. GitHub documentation currently describes artifact attestations alone as SLSA v1.0 Build Level 2 and reusable-workflow designs as helping achieve v1 Build Level 3. The current SLSA specification is v1.2; this lab does not claim independent certification or evaluate every Build L3 requirement.”
That wording is intentionally narrower than “SLSA compliant.” Production teams should name the specification version/track, assess the actual builder and producer process, and retain evidence for the claim they make.
11. Final verification and cleanup
-
Original
atlas-lab.tarpasses SHA-256 checksum and provenance verification. - SPDX predicate verifies separately for the same subject digest.
- Tampered copy has a different digest and a non-zero verification result.
- Verification constrains repository, signer workflow, source ref and hosted-runner policy.
- REST inventory and workflow run/head SHA are recorded as supporting metadata, not substituted for cryptographic verification.
- Deployment gate and SLSA-oriented statement are documented with exception/expiry ownership.
# Preserve local evidence; then archive the disposable repository.
ls -la
jq '.[0].verificationResult.statement | {subject,predicateType}' provenance.json
jq '.[0].verificationResult.statement | {subject,predicateType}' sbom-verification.json
cd ../ch25-checkpoint
git status --short
gh repo archive "$REPO" --yes
Knowledge check
The tar verifies under --repo but fails under
--signer-workflow. What does that diagnose?
The artifact has an attestation associated with the repository, but the authenticated signer workflow does not match the workflow your policy requires. Investigate the builder instead of weakening the rule.
Why does the tampered file fail even though its filename is almost the same?
Verification hashes the bytes; the one-byte change produces a different subject digest with no matching accepted attestation.
What relationship should provenance and SBOM have in this checkpoint?
They are different predicates bound to the same immutable artifact subject. Provenance describes the build; the SBOM describes inventory.
Why is REST attestation count not enough for deployment?
Listing proves presence under API visibility, not cryptographic signature/timestamp validity or authorized signer identity.
What does
--deny-self-hosted-runners express?
A consumer trust decision that only attestations generated on accepted non-self-hosted runner infrastructure are allowed. It is policy, not a universal claim that self-hosted runners are insecure.
Can this checkpoint claim “software is secure” because all checks pass?
No. It proves origin/integrity/inventory evidence under the stated policy. Vulnerability, behavior, secret, dependency and runtime risks remain separate controls.
12. Production operating model and Chapter 26 bridge
Chapter 25 adds a consumer-verifiable supply-chain evidence layer to the GitHub operating model: immutable subject digests, authenticated workflow provenance, attested SBOM predicates, explicit repository/workflow/ref/runner trust, tamper rejection, and a cautious versioned SLSA-oriented claim. The central rule is simple: producers generate evidence, but consumers receive the security benefit only when they verify the exact artifact under an authorization policy.
Chapter 26 now generalizes the machine-readable techniques used
throughout this course. You will move from individual
gh api calls into systematic REST/GraphQL clients with
pagination, rate-limit handling, safe mutation semantics, and
automation design.
Official references
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.
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0
Send only Ethereum/ERC-20 compatible assets to this
address.