Chapter 23Lesson 05260–360 min

Checkpoint Lab — SBOMs, Component Provenance, Vulnerability Governance, Repository Firewall Concepts, and Supply-Chain Risk

Build three synthetic evidence packets, predict their dispositions, change only vulnerability intelligence for one component, and prove which facts are immutable. The deliverable is an auditable runbook/evidence bundle—not a screenshot of a green build.

Checkpoint labEvidence packetThree componentsPolicy re-evaluationRunbook

Learning objectives

  • Create three synthetic artifacts with coordinates/digests and retain their exact hashes.
  • Build SBOM and provenance fixtures that distinguish first-party and third-party evidence quality.
  • Evaluate time-stamped vulnerability/policy snapshots and classify allow, quarantine, and hold decisions.
  • Prove which facts remain immutable when intelligence and governance decisions change later.
  • Produce a verification/cleanup runbook and bridge the evidence model into Docker registry operations.
Version/product baseline (27 August 2026). The dated Nexus reference line is 3.95.2-01 with Java 21. Repository Firewall/IQ examples are architecture or fixture exercises unless the learner already has an authorized license. Re-check live release notes, feature matrix, and integration documentation before applying version-specific UI/API steps.
Evidence boundary. A checksum/digest can prove byte identity relative to an expected value; an SBOM inventories declared/discovered components; provenance describes build origin/process; a signature authenticates according to its key/trust model; vulnerability intelligence is time-varying; policy is a governance decision. None of these alone proves that software is safe.
Safety boundary. Use only synthetic artifacts, loopback/private endpoints, fake namespaces, and local fixtures. Do not fetch suspicious packages for demonstrations, publish employer SBOMs, expose provenance secrets, delete production artifacts because a CVE appears, or bypass governed Nexus endpoints to make a build pass.

1. Checkpoint scenario

You are the artifact-platform operator for a small fictional service. Three components must be reviewed:

Component Origin Initial evidence Expected challenge
alpha-service 1.0.0 Internal build Artifact, SBOM, matching provenance subject, no known vulnerability in snapshot A. Should remain allowed if evidence stays consistent.
bravo-lib 2.4.0 Third party Artifact + SBOM; no internal provenance claim; clean snapshot A. Snapshot B introduces a synthetic high-severity vulnerability.
charlie-tool 3.1.0 Internal build Artifact + SBOM + provenance whose subject digest intentionally does not match. Hold for identity/provenance investigation even if vulnerability snapshot is clean.

2. Preflight and safety

  • Use Python 3 in a new local directory chapter23-checkpoint.
  • Do not use real package namespaces, real CVEs, real signing keys, public registries, production Nexus, or employer SBOMs.
  • Optional Nexus mapping must use disposable hosted repositories only.
  • Do not delete/reclaim any real repository content.
License assumption: the mandatory lab uses no Sonatype IQ/Firewall license. A licensed deployment analogue may be documented after the fixture exercise, but it is not required.

3. Required predictions before execution

  1. Alpha should be allow because digest/provenance match and current synthetic risk is below threshold.
  2. Bravo should be allow under snapshot A and quarantine under snapshot B while its artifact digest/SBOM remain unchanged.
  3. Charlie should be hold before vulnerability policy because provenance subject mismatch breaks the claimed build-to-artifact link.

Write these predictions into predictions.md before running the evaluator.

4. Create the complete checkpoint fixture

from pathlib import Path
import hashlib, json

root = Path('chapter23-checkpoint')
root.mkdir(exist_ok=True)

specs = {
 'alpha-service-1.0.0.bin': b'alpha-service\nversion=1.0.0\nbuild=alpha-100\n',
 'bravo-lib-2.4.0.bin': b'bravo-lib\nversion=2.4.0\nsource=third-party-fixture\n',
 'charlie-tool-3.1.0.bin': b'charlie-tool\nversion=3.1.0\nbuild=charlie-310\n',
}

def sha(data): return hashlib.sha256(data).hexdigest()

digests = {}
for name, data in specs.items():
    (root/name).write_bytes(data)
    digests[name] = sha(data)

sbom = {
 'bomFormat':'CycloneDX', 'specVersion':'1.6', 'version':1,
 'components':[
   {'type':'application','name':'alpha-service','version':'1.0.0','purl':'pkg:generic/learner-example/alpha-service@1.0.0'},
   {'type':'library','name':'bravo-lib','version':'2.4.0','purl':'pkg:generic/learner-example/bravo-lib@2.4.0'},
   {'type':'application','name':'charlie-tool','version':'3.1.0','purl':'pkg:generic/learner-example/charlie-tool@3.1.0'}
 ]
}

provenance = {
 'alpha-service-1.0.0.bin': {
   '_type':'https://in-toto.io/Statement/v1',
   'subject':[{'name':'alpha-service-1.0.0.bin','digest':{'sha256':digests['alpha-service-1.0.0.bin']}}],
   'predicateType':'https://slsa.dev/provenance/v1',
   'predicate':{'runDetails':{'builder':{'id':'https://builder.example.invalid/internal-ci'}},
                'buildDefinition':{'buildType':'https://builder.example.invalid/types/internal/v1','externalParameters':{'commit':'ALPHA_FAKE_COMMIT'}}}
 },
 'charlie-tool-3.1.0.bin': {
   '_type':'https://in-toto.io/Statement/v1',
   'subject':[{'name':'charlie-tool-3.1.0.bin','digest':{'sha256':'0'*64}}],
   'predicateType':'https://slsa.dev/provenance/v1',
   'predicate':{'runDetails':{'builder':{'id':'https://builder.example.invalid/internal-ci'}},
                'buildDefinition':{'buildType':'https://builder.example.invalid/types/internal/v1','externalParameters':{'commit':'CHARLIE_FAKE_COMMIT'}}}
 }
}

snapshot_a = {
 'evaluatedAt':'2026-08-27T00:00:00Z',
 'risk':{
   'pkg:generic/learner-example/alpha-service@1.0.0':0.0,
   'pkg:generic/learner-example/bravo-lib@2.4.0':0.0,
   'pkg:generic/learner-example/charlie-tool@3.1.0':0.0
 }
}
snapshot_b = json.loads(json.dumps(snapshot_a))
snapshot_b['evaluatedAt'] = '2026-09-26T00:00:00Z'
snapshot_b['risk']['pkg:generic/learner-example/bravo-lib@2.4.0'] = 8.8

(root/'digests.json').write_text(json.dumps(digests,indent=2)+'\n')
(root/'sbom.cdx.json').write_text(json.dumps(sbom,indent=2)+'\n')
(root/'provenance.json').write_text(json.dumps(provenance,indent=2)+'\n')
(root/'intel-a.json').write_text(json.dumps(snapshot_a,indent=2)+'\n')
(root/'intel-b.json').write_text(json.dumps(snapshot_b,indent=2)+'\n')
print(json.dumps(digests, indent=2))

5. Evaluate identity first, then mutable risk

from pathlib import Path
import hashlib, json

root = Path('chapter23-checkpoint')
digests = json.loads((root/'digests.json').read_text())
prov = json.loads((root/'provenance.json').read_text())

purls = {
 'alpha-service-1.0.0.bin':'pkg:generic/learner-example/alpha-service@1.0.0',
 'bravo-lib-2.4.0.bin':'pkg:generic/learner-example/bravo-lib@2.4.0',
 'charlie-tool-3.1.0.bin':'pkg:generic/learner-example/charlie-tool@3.1.0',
}

def actual_digest(name):
    return hashlib.sha256((root/name).read_bytes()).hexdigest()

def provenance_ok(name):
    if name == 'bravo-lib-2.4.0.bin':
        return None  # third-party fixture: no internal provenance claim
    return prov[name]['subject'][0]['digest']['sha256'] == actual_digest(name)

def evaluate(snapshot_name):
    intel = json.loads((root/snapshot_name).read_text())
    rows = []
    for name, purl in purls.items():
        identity_ok = actual_digest(name) == digests[name]
        prov_ok = provenance_ok(name)
        risk = intel['risk'][purl]
        if not identity_ok or prov_ok is False:
            decision = 'hold'
            reason = 'identity/provenance mismatch'
        elif risk >= 7.0:
            decision = 'quarantine'
            reason = 'synthetic risk threshold'
        else:
            decision = 'allow'
            reason = 'identity checks pass; risk below threshold'
        rows.append({'artifact':name,'sha256':actual_digest(name),
                     'provenanceOk':prov_ok,'risk':risk,
                     'decision':decision,'reason':reason,
                     'evaluatedAt':intel['evaluatedAt']})
    return rows

for src, out in [('intel-a.json','decision-a.json'),('intel-b.json','decision-b.json')]:
    result = evaluate(src)
    (root/out).write_text(json.dumps(result,indent=2)+'\n')
    print(src, [(r['artifact'], r['decision']) for r in result])

Expected:

intel-a.json [('alpha-service-1.0.0.bin', 'allow'), ('bravo-lib-2.4.0.bin', 'allow'), ('charlie-tool-3.1.0.bin', 'hold')]
intel-b.json [('alpha-service-1.0.0.bin', 'allow'), ('bravo-lib-2.4.0.bin', 'quarantine'), ('charlie-tool-3.1.0.bin', 'hold')]

6. Interpret the results correctly

  • Alpha: nothing changed. Its exact bytes and evidence remain internally consistent.
  • Bravo: its SHA-256 and SBOM entry are identical across both runs; only risk intelligence and policy disposition changed.
  • Charlie: its vulnerability snapshot is clean, but it is still held because the claimed provenance subject does not match the bytes. “No CVE” cannot repair broken provenance.

7. Prove immutable versus mutable facts

Fact Alpha Bravo Charlie Expected to change later?
Artifact SHA-256 Fixed Fixed Fixed No, unless bytes change.
SBOM name/version for this build Fixed Fixed Fixed No for archived build evidence.
Provenance subject Fixed + matches Not claimed internally Fixed + intentionally mismatches Do not rewrite historical evidence.
Vulnerability risk 0 → 0 0 → 8.8 0 → 0 Yes.
Disposition allow → allow allow → quarantine hold → hold Yes as evidence/policy changes.

8. Required evidence packet

chapter23-checkpoint/
├── predictions.md
├── alpha-service-1.0.0.bin
├── bravo-lib-2.4.0.bin
├── charlie-tool-3.1.0.bin
├── digests.json
├── sbom.cdx.json
├── provenance.json
├── intel-a.json
├── intel-b.json
├── decision-a.json
├── decision-b.json
├── nexus-mapping.md
└── verification.md

In nexus-mapping.md, record where each artifact/evidence object would live in a production design and explicitly distinguish Nexus database/blob state from SBOM/provenance/intelligence/policy state. In verification.md, record the predictions, actual decisions, hashes, and all checklist results.

9. Optional disposable Nexus mapping

If you have the disposable local Nexus instance from earlier chapters, create a hosted repository specifically for harmless checkpoint artifacts or use an existing disposable lab repository. Publish the three files through the supported repository protocol, calculate SHA-256 after downloading through Nexus, and prove the downloaded digest matches digests.json. Store the evidence files only if your chosen Raw/Maven evidence design is deliberate.

Do not pretend that uploading provenance.json to Nexus validates it. Nexus is preserving bytes; provenance verification is a separate control.

10. Optional licensed IQ/Firewall mapping

In an authorized licensed environment only, map the fixture concepts to current Sonatype behavior: third-party component request → component intelligence → policy evaluation → repository disposition. Keep the fixture as the mandatory proof so learners do not need paid services or real risky packages. Never search for malware to create a quarantine demo.

11. Verification checklist

  • All three artifact hashes were calculated from local bytes.
  • Alpha provenance subject exactly matches Alpha SHA-256.
  • Charlie provenance subject intentionally mismatches and the evaluator returns hold.
  • Bravo SHA-256 is identical under both intelligence snapshots.
  • Bravo changes only from allow to quarantine because risk changed from 0.0 to 8.8.
  • The SBOM remains unchanged between evaluations.
  • No real CVE/package/employer identifiers, credentials, public uploads, or production URLs are used.
  • Nexus mapping clearly separates database/blob state from external evidence/governance state.
  • Predictions were written before execution and compared with results.

12. Cleanup / rollback

Archive the evidence packet if desired, then delete only the local chapter23-checkpoint directory. If an optional Nexus lab was used, remove only the explicitly named disposable repository/content through supported Nexus UI/API after inspecting scope. Do not delete blob-store files or database rows manually. If a licensed policy fixture was used, remove only the authorized test configuration and restore its documented baseline.

13. Knowledge check

Why is Charlie held even though its vulnerability risk is 0?

Which Bravo facts changed between snapshot A and B?

Should you edit Charlie provenance to the correct digest and continue?

What would Repository Firewall add to the Bravo story in a licensed deployment?

Why does the checkpoint use a synthetic CVE and no public package?

14. Chapter summary and bridge to Chapter 24

Chapter 23 completes the supply-chain evidence model around Nexus: exact artifact bytes are anchored by digests; SBOMs inventory components; provenance describes build origin/process; signatures and TLS provide different integrity/authentication properties; vulnerability intelligence evolves; and repository policy governs consumption without becoming proof of safety. The checkpoint proved that immutable and mutable facts must be stored separately but linked. Chapter 24 applies that discipline to Docker/OCI registry operations, where tags, manifests, layer digests, proxy caches, cleanup, retention, and production troubleshooting introduce another set of identity and reclamation traps.

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
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0 Send only Ethereum/ERC-20 compatible assets to this address.