Checkpoint Lab — SBOM Generation, Dependency Lists, Vulnerability Reports, Policy Evaluation, and Supply-Chain Evidence
Produce and verify a complete disposable supply-chain evidence bundle, inject one synthetic policy-relevant finding, remediate it without changing the evidence history, and state exactly what the bundle proves and does not prove.
Learning objectives
Checkpoint objectives
- Produce a CycloneDX 1.6 SBOM and bind it to an exact source SHA and artifact SHA-256.
- Inject one deterministic synthetic vulnerability finding and prove policy blocks for the documented reason.
- Remediate the lab by changing the synthetic dependency identity and produce a second evidence packet without overwriting the first.
- Verify raw evidence independently of any GitLab security UI.
- State exactly what the bundle proves—and what it cannot prove.
1. Checkpoint scenario
You own a disposable project named
glci-ch26-checkpoint. Pipeline A builds a tiny
artifact, generates a CycloneDX 1.6 SBOM, correlates one synthetic
High-severity finding, and blocks. Pipeline B changes the lab
dependency version so the synthetic advisory no longer matches,
builds a new artifact/evidence lineage, and allows. You preserve
both bundles.
2. Assumptions and preflight
| Item | Assumption | Evidence to record |
|---|---|---|
| GitLab | Free path is sufficient; Ultimate integrations optional | GitLab offering/version page or course assumptions note. |
| Runner | Authorized disposable runner; no privileged requirement | Runner ID/description/executor from job metadata/log. |
| Image | python:3.13-alpine used in examples |
Resolved image metadata/digest if your runner exposes it. |
| SBOM tool | cyclonedx-bom==7.3.1 |
cyclonedx-py --version output. |
| SBOM schema | CycloneDX 1.6 | specVersion in raw JSON. |
| Advisory data | Synthetic DB chapter26-synthetic-db-1 |
Raw vulnerability JSON. |
| Policy | chapter26-no-unexcepted-high-v1 |
Policy source SHA + policy-result JSON. |
Before changing anything, record CI_PIPELINE_SOURCE,
CI_COMMIT_SHA, pipeline ID, and the expected source
branch/ref.
3. Predict before execution
Write down at least these predictions:
- Pipeline A: build/SBOM jobs succeed; one synthetic High finding appears; policy job fails; no production/external state changes.
- Lineage: artifact SHA-256, SBOM SHA-256, source SHA, pipeline/job IDs, generator version, vulnerability DB identity, and policy decision are all recorded in the evidence packet.
-
Pipeline B: after the synthetic remediation,
artifact/SBOM digests change and policy becomes
allow; Pipeline A's blocked evidence remains unchanged.
4. Checkpoint files
app/message.txt
requirements.txt
tools/make_synthetic_findings.py
tools/make_manifest.py
policy/evaluate.py
.gitlab-ci.yml
# tools/make_manifest.py
from pathlib import Path
import hashlib, json, os
def sha256(path):
h = hashlib.sha256()
with open(path, "rb") as fh:
for chunk in iter(lambda: fh.read(1024 * 1024), b""):
h.update(chunk)
return h.hexdigest()
manifest = {
"pipeline_id": os.environ.get("CI_PIPELINE_ID", "local"),
"job_id": os.environ.get("CI_JOB_ID", "local"),
"pipeline_source": os.environ.get("CI_PIPELINE_SOURCE", "local"),
"source_sha": os.environ.get("CI_COMMIT_SHA", "local"),
"artifact": {"path":"dist/app.txt", "sha256":sha256("dist/app.txt")},
"sbom": {"path":"evidence/sbom.cdx.json", "sha256":sha256("evidence/sbom.cdx.json")},
"vulnerability_report": {"path":"evidence/vulnerabilities.json", "sha256":sha256("evidence/vulnerabilities.json")},
}
Path("evidence/manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
print(json.dumps(manifest, indent=2))
5. Exact checkpoint pipeline
stages: [build, evidence, policy]
build:
stage: build
image: python:3.13-alpine
script:
- mkdir -p dist evidence
- cp app/message.txt dist/app.txt
- sha256sum dist/app.txt | tee evidence/artifact.sha256
- printf 'pipeline_source=%s\nsource_sha=%s\npipeline_id=%s\njob_id=%s\n' "$CI_PIPELINE_SOURCE" "$CI_COMMIT_SHA" "$CI_PIPELINE_ID" "$CI_JOB_ID" > evidence/source.txt
artifacts:
expire_in: 14 days
paths: [dist/, evidence/]
sbom:
stage: evidence
image: python:3.13-alpine
needs:
- job: build
artifacts: true
script:
- python -m venv .venv
- . .venv/bin/activate
- python -m pip install --disable-pip-version-check 'cyclonedx-bom==7.3.1'
- cyclonedx-py --version | tee evidence/generator-version.txt
- cyclonedx-py requirements requirements.txt --spec-version 1.6 --output-format json --output-file evidence/sbom.cdx.json
- python tools/make_synthetic_findings.py
- python tools/make_manifest.py
- sha256sum evidence/sbom.cdx.json evidence/vulnerabilities.json evidence/manifest.json | tee evidence/SHA256SUMS
artifacts:
when: always
expire_in: 14 days
paths: [dist/, evidence/]
policy:
stage: policy
image: python:3.13-alpine
needs:
- job: sbom
artifacts: true
script:
- python policy/evaluate.py
artifacts:
when: always
expire_in: 14 days
paths: [evidence/]
6. Run A: expected block
Set requirements.txt to:
requests==2.32.5
The synthetic correlation script should emit
CVE-LAB-2026-0001 with severity High. The policy job
should fail. Preserve the pipeline ID and download the evidence
artifacts before making the remediation commit.
Expected evidence:
SBOM: CycloneDX 1.6
component: pkg:pypi/requests@2.32.5
synthetic_db: chapter26-synthetic-db-1
finding: CVE-LAB-2026-0001 severity=HIGH
policy: chapter26-no-unexcepted-high-v1
policy_decision: block
artifact_sha256: <exact value from run A>
sbom_sha256: <exact value from run A>
7. Diagnose before remediation
Do not assume “policy failed” means the pipeline configuration is broken. Verify:
- The build artifact digest exists and matches the manifest.
-
The SBOM
specVersionis 1.6 and includes the expected PURL. - The vulnerability report references that exact PURL and the expected synthetic DB.
-
The policy source/version is
chapter26-no-unexcepted-high-v1. - The policy blocks because the High finding is unexcepted—not because a file is missing or JSON is malformed.
8. Run B: synthetic remediation and new lineage
Change only the lab dependency identity to a non-matching synthetic version:
requests==2.32.6
Commit the change. Pipeline B must produce a new source SHA and new SBOM digest. The synthetic DB no longer matches the exact PURL, so the policy allows. Do not overwrite Pipeline A's evidence; the contrast is the point of the checkpoint.
9. Independent verification checklist
- Pipeline A and B IDs are different and recorded.
-
CI_COMMIT_SHAdiffers between remediation runs. - Each artifact's SHA-256 matches its own manifest.
-
Each SBOM checksum validates and
specVersionis 1.6. - Run A contains the synthetic finding; Run B does not.
- Run A policy result is block; Run B policy result is allow.
- No production environment, registry, cloud account, or external target changed.
- If optional GitLab Ultimate ingestion is enabled, UI state agrees with raw evidence, but raw artifacts remain the primary checkpoint evidence.
10. Required evidence packet
| Evidence | Required content |
|---|---|
| Source/pipeline | Pipeline source/ref/SHA, pipeline ID, build/SBOM/policy job IDs. |
| Build | Artifact filename and SHA-256; runner/executor/image/tool assumptions. |
| SBOM | Raw CycloneDX 1.6 JSON, SBOM SHA-256, generator version, component/PURL list. |
| Vulnerability correlation | Synthetic DB identity, raw finding report, finding IDs/severity/component. |
| Policy | Policy source/version, block/allow result, exceptions if any. |
| Comparison | Run A versus Run B source/artifact/SBOM digests and policy outcomes. |
| Limitations | Explicit statement of what the bundle does and does not prove. |
11. Required limitations statement
Your evidence packet must include a statement equivalent to:
12. Cleanup and rollback
# Local cleanup only
rm -rf .venv dist evidence
# GitLab cleanup:
# - keep the two checkpoint pipelines/evidence until you have completed review;
# - then delete only the exact disposable project if it exists solely for this lab;
# - do not bulk-delete security findings, artifacts, packages, or projects by wildcard.
Knowledge check
Why must Run A evidence remain after Run B succeeds?
Run A is the original blocked decision and demonstrates the causal change. Overwriting it destroys auditability and makes the remediation story unverifiable.
The SBOM lists requests 2.32.5 and policy blocks. What additional evidence shows the delivered artifact is the one this SBOM describes?
The artifact SHA-256 bound in the evidence manifest to the same source/pipeline, plus independent verification of that digest when the artifact is consumed.
If GitLab Dependency List shows no vulnerability, may you claim the artifact is secure?
No. The UI is derived from available inventory/advisory data. Absence of a displayed finding is not proof of absence of vulnerabilities or other security problems.
Why is a policy exception a separate object from the SBOM?
The SBOM describes inventory. An exception is governance metadata about accepting a finding/risk temporarily; mixing them would corrupt the inventory evidence.
What is the safest next capability after this chapter for external deployments?
Short-lived workload identity: Chapter 27 uses GitLab ID tokens/OIDC to replace long-lived cloud credentials where providers support it.
13. What Chapter 26 adds to the production operating model
You can now build a supply-chain evidence chain that remains inspectable even without a paid security UI: exact source → exact artifact digest → CycloneDX component inventory → vulnerability-data correlation → explicit policy/exception → retained evidence → consumer verification. You also know the limit: better evidence improves traceability and governance, but it does not turn an inventory or provenance record into a proof of security.
Version and compatibility note
GitLab and GitLab Runner evolve continuously. Treat version-sensitive YAML, runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations as assumptions to verify against the current official GitLab documentation before production use. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for any reproducible lab or incident record.
Official references and version notes
Documentation verification date: 2026-09-12. GitLab
currently documents the Dependency List,
artifacts:reports:cyclonedx, Vulnerability Report, and
organization-wide security policies as Ultimate features. The
mandatory course path therefore keeps the SBOM, vulnerability
correlation, policy decision, hashes, and evidence bundle as
ordinary artifacts and local scripts so it remains
Free/disposable-compatible. GitLab's Dependency List can ingest
CycloneDX 1.4, 1.5, or 1.6 documents from the latest default-branch
pipeline. Security-policy evaluation trusts scanner artifact
reports; policy evaluation does not itself prove the authenticity or
integrity of the scanner that produced them. The lab pins
cyclonedx-bom==7.3.1, released 2026-07-23, and requests
CycloneDX 1.6 explicitly. The checkpoint uses a synthetic finding so
the lesson remains deterministic. In production, vulnerability
databases and severity assessments evolve; preserve scanner/database
identity and re-evaluate deliberately rather than treating old
results as timeless truth.
- Dependency list — official reference.
- Dependency scanning by using SBOM — official reference.
- Continuous dependency scanning — official reference.
- CI/CD artifacts report types — official reference.
- CI/CD YAML syntax — official reference.
- Security scanning results — official reference.
- Vulnerability report — official reference.
- Security policies — official reference.
- Merge request approval policies — official reference.
- Scan execution policies — official reference.
- CycloneDX Python SBOM generator — official reference.
- CycloneDX specification 1.6 — official reference.
- Anchore Syft — official reference.
Current assumptions used in this chapter: Version-sensitive YAML, Runner/executor behavior, APIs, security features, policy controls, tiers, and deprecations must be verified against the exact GitLab, GitLab Runner, tool, and external-system versions used in production. Preserve the exact project/ref/SHA, compiled configuration, pipeline/job IDs, runner/tool versions, and external target evidence used for reproducible work.
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.