Chapter 26Lesson 05~235 minutes

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.

CheckpointSBOM verificationPolicy outcomeRemediationLimitations

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.

Safety guard: all component/advisory names in the policy exercise are synthetic. Do not replace them with a production repository, real customer artifact, or live credential. Cleanup only the exact disposable project/resources created for this lab.

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:

  1. Pipeline A: build/SBOM jobs succeed; one synthetic High finding appears; policy job fails; no production/external state changes.
  2. 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.
  3. 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:

  1. The build artifact digest exists and matches the manifest.
  2. The SBOM specVersion is 1.6 and includes the expected PURL.
  3. The vulnerability report references that exact PURL and the expected synthetic DB.
  4. The policy source/version is chapter26-no-unexcepted-high-v1.
  5. 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_SHA differs between remediation runs.
  • Each artifact's SHA-256 matches its own manifest.
  • Each SBOM checksum validates and specVersion is 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:

This bundle proves that the recorded pipeline generated the recorded artifact digest, that the recorded SBOM generator observed the listed components under its documented scope, that the synthetic vulnerability dataset produced the recorded finding set, and that policy v1 produced the recorded decision. It does not prove that the source/build environment was uncompromised, that every dependency was discovered, that advisory data was complete, that every finding is exploitable, that no unknown vulnerability exists, or that the artifact was deployed to production.

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?

The SBOM lists requests 2.32.5 and policy blocks. What additional evidence shows the delivered artifact is the one this SBOM describes?

If GitLab Dependency List shows no vulnerability, may you claim the artifact is secure?

Why is a policy exception a separate object from the SBOM?

What is the safest next capability after this chapter for external deployments?

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.

Next chapter

ID Tokens, OIDC Workload Identity, Cloud Federation, Vault Authentication, and Secretless Deployments

Use that evidence-oriented mindset for identity itself: request narrowly scoped GitLab ID tokens, validate OIDC audiences and claims, and exchange them for short-lived provider/Vault credentials instead of storing long-lived cloud secrets.

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.

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.

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