Chapter 23Lesson 02220–300 min

SBOMs, Component Provenance, Vulnerability Governance, Repository Firewall Concepts, and Supply-Chain Risk: Guided Hands-On Workflow and Core Operations

Create one harmless artifact and a complete local evidence packet around it. Then change only the vulnerability-intelligence snapshot and observe how the policy disposition can change while the SHA-256, SBOM inventory, and provenance subject remain fixed.

Hands-onCycloneDX fixtureSLSA-style provenanceRe-evaluationEvidence packet

Learning objectives

  • Generate a synthetic artifact and independently calculate its SHA-256 digest.
  • Create a compact CycloneDX SBOM fixture whose component identity is linked to the artifact/build context.
  • Create a SLSA-style provenance fixture tied to the exact artifact digest without claiming a real trusted builder.
  • Evaluate two time-stamped vulnerability snapshots and explain why the disposition changes without changing artifact bytes.
  • Package evidence without credentials and map each file to Nexus, build, intelligence, or governance state.
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. Disposable scenario and preflight

The mandatory path needs only Python 3 and a temporary directory. Nexus Community is optional: if a disposable Nexus instance from earlier chapters is running, you may upload the harmless artifact to a disposable hosted Raw or Maven repository through the supported package path, but the evidence exercise works entirely offline.

Lab names: artifact learner-widget-1.0.0.bin; package identity pkg:generic/learner-example/learner-widget@1.0.0; fake builder https://builder.example.invalid/local-ci; evidence directory chapter23-evidence.

Preflight: do not use a production Nexus URL, public registry, employer namespace, real signing key, or real vulnerability feed. This lab models evidence semantics, not malware analysis.

2. Predict before executing

Write two predictions:

  1. Changing the vulnerability snapshot should not change the artifact SHA-256, the SBOM component version, or the provenance subject digest.
  2. A policy disposition can change from allow to quarantine when new intelligence exceeds the synthetic threshold, even though the artifact bytes remain identical.

3. Create the artifact and immutable byte identity

mkdir chapter23-evidence
cd chapter23-evidence
printf 'learner-widget
version=1.0.0
build=run-0042
' > learner-widget-1.0.0.bin
sha256sum learner-widget-1.0.0.bin

PowerShell learners can create the same text file with Set-Content and calculate the digest with Get-FileHash -Algorithm SHA256. Record the complete hex digest. Every later evidence object must refer to this exact value.

4. Generate SBOM, provenance, and vulnerability snapshots

The following script is intentionally local and deterministic. It does not call Nexus, Sonatype, NVD, OSV, or any public service. The SBOM uses a minimal CycloneDX shape for teaching; the provenance fixture borrows the current SLSA provenance vocabulary but is explicitly unsigned synthetic evidence, not a claim of SLSA conformance.

from pathlib import Path
import hashlib, json, datetime

root = Path('.')
artifact = root / 'learner-widget-1.0.0.bin'
sha256 = hashlib.sha256(artifact.read_bytes()).hexdigest()

sbom = {
  "bomFormat": "CycloneDX",
  "specVersion": "1.6",
  "version": 1,
  "metadata": {
    "component": {
      "type": "application",
      "name": "learner-widget",
      "version": "1.0.0",
      "purl": "pkg:generic/learner-example/learner-widget@1.0.0"
    }
  },
  "components": [
    {"type":"library", "name":"tiny-parser", "version":"2.1.0",
     "purl":"pkg:generic/learner-example/tiny-parser@2.1.0"}
  ]
}

provenance = {
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [{"name": artifact.name, "digest": {"sha256": sha256}}],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://builder.example.invalid/types/local-fixture/v1",
      "externalParameters": {"sourceCommit": "0123456789abcdefFAKE"},
      "resolvedDependencies": []
    },
    "runDetails": {
      "builder": {"id": "https://builder.example.invalid/local-ci"},
      "metadata": {"invocationId": "run-0042"}
    }
  }
}

intel_day1 = {
  "evaluatedAt": "2026-08-27T00:00:00Z",
  "component": "pkg:generic/learner-example/tiny-parser@2.1.0",
  "knownVulnerabilities": [], "maxSeverity": 0.0
}
intel_day30 = {
  "evaluatedAt": "2026-09-26T00:00:00Z",
  "component": "pkg:generic/learner-example/tiny-parser@2.1.0",
  "knownVulnerabilities": [{"id":"CVE-2099-0001", "severity":8.4}],
  "maxSeverity": 8.4
}

for name, obj in {
  "sbom.cdx.json": sbom,
  "provenance.intoto.json": provenance,
  "intel-day1.json": intel_day1,
  "intel-day30.json": intel_day30,
}.items():
    (root/name).write_text(json.dumps(obj, indent=2) + "\n", encoding="utf-8")

(root/'artifact.sha256').write_text(f"{sha256}  {artifact.name}\n", encoding='utf-8')
print(sha256)

Save it as make_evidence.py and run python make_evidence.py. The fake CVE ID is intentionally synthetic.

5. Evaluate mutable intelligence against a fixed artifact

import json
from pathlib import Path

THRESHOLD = 7.0
for filename in ['intel-day1.json', 'intel-day30.json']:
    intel = json.loads(Path(filename).read_text())
    decision = 'quarantine' if intel['maxSeverity'] >= THRESHOLD else 'allow'
    result = {
      'artifactSha256': Path('artifact.sha256').read_text().split()[0],
      'evaluatedAt': intel['evaluatedAt'],
      'policy': 'synthetic-max-severity-gte-7',
      'policyVersion': '2026-08-27.1',
      'decision': decision,
      'evidenceSource': filename
    }
    Path(filename.replace('intel-', 'decision-')).write_text(
        json.dumps(result, indent=2) + '\n')
    print(filename, decision)

Expected output:

intel-day1.json allow
intel-day30.json quarantine

The vulnerability state changed; the artifact did not. That distinction is the core of vulnerability governance.

6. Verify the invariant explicitly

import hashlib, json
from pathlib import Path

artifact = Path('learner-widget-1.0.0.bin')
actual = hashlib.sha256(artifact.read_bytes()).hexdigest()
expected = Path('artifact.sha256').read_text().split()[0]
prov = json.loads(Path('provenance.intoto.json').read_text())

assert actual == expected
assert prov['subject'][0]['digest']['sha256'] == actual
print('artifact digest unchanged:', actual)
print('day1:', json.loads(Path('decision-day1.json').read_text())['decision'])
print('day30:', json.loads(Path('decision-day30.json').read_text())['decision'])

If this assertion fails, stop. A policy discussion is secondary until artifact identity is restored.

7. Optional Nexus mapping: where would these objects go?

Object Mandatory fixture location Possible production location
Artifact bytes Local lab file Nexus hosted/proxy blob + database metadata through supported repository protocol.
SBOM sbom.cdx.json Build evidence store, governed Raw repository, SBOM manager, or application-security platform according to policy.
Provenance provenance.intoto.json Attestation/evidence service or repository/package ecosystem that supports provenance distribution.
Vulnerability snapshot intel-day*.json Scanner/IQ/Lifecycle/reporting platform; do not treat the SBOM copy as forever-current.
Policy decision decision-day*.json Governance/Firewall/CI policy record with timestamp, policy version, and owner.

Nexus can safely store generic evidence as ordinary artifacts in a deliberately designed Raw repository if that fits organizational policy, but Nexus storage alone does not validate the evidence or turn it into trusted provenance.

8. Small challenge: choose the evidence, not the tool

For each question, choose the minimum evidence needed:

  • “Did deployment use the same bytes as the build?” → compare immutable digest.
  • “Which dependencies were represented?” → inspect the build’s SBOM.
  • “Which source/build produced this artifact?” → verify provenance/attestation.
  • “Is a dependency newly known to be vulnerable?” → query current intelligence using component identity.
  • “Why was consumption allowed last month?” → inspect the historical policy decision, evidence timestamp, policy version, and waiver state.

9. Verification and cleanup

  • Artifact digest equals the saved SHA-256.
  • Provenance subject digest equals artifact SHA-256.
  • SBOM component/version does not change between evaluations.
  • Day-1 decision is allow; day-30 decision is quarantine.
  • No credentials, production URLs, or real package names appear in the evidence.

Cleanup is simply deleting the disposable chapter23-evidence directory. If you used an optional disposable Nexus repository, delete only that lab repository through supported Nexus mechanisms after verifying its name and scope; never delete blob files manually.

10. Knowledge check

Why does the day-30 policy change not invalidate the day-1 SBOM?

Why is the provenance fixture explicitly described as unsigned synthetic evidence?

What should happen if the provenance subject digest differs from the artifact digest?

Can a Raw hosted repository be used to retain SBOM files?

What changed between the two policy evaluations?

11. Summary and next step

You built a complete evidence packet and proved the most important invariant: immutable artifact evidence can stay fixed while risk intelligence and policy disposition evolve. Lesson 3 turns that mechanic into architecture decisions about central governance, scanners, allowlists/denylists, internal provenance, quarantine, evidence retention, and developer experience.

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.