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.
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.
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.
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:
- Changing the vulnerability snapshot should not change the artifact SHA-256, the SBOM component version, or the provenance subject digest.
-
A policy disposition can change from
allowtoquarantinewhen 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 isquarantine. - 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?
The SBOM remains a point-in-time inventory of that build. The mutable element is vulnerability intelligence and the resulting policy decision, not the inventory or artifact bytes.
Why is the provenance fixture explicitly described as unsigned synthetic evidence?
Because a JSON shape alone does not make a trustworthy attestation. Trust requires an authenticated producer/verifier model and integrity protections appropriate to the provenance system.
What should happen if the provenance subject digest differs from the artifact digest?
Stop the trust chain for that artifact and investigate. Do not “fix” the evidence by editing the digest to match after the fact.
Can a Raw hosted repository be used to retain SBOM files?
Yes, as generic stored artifacts if the organization designs it that way, but storage does not automatically validate SBOM completeness, freshness, or provenance.
What changed between the two policy evaluations?
The vulnerability-intelligence snapshot and resulting disposition changed; artifact bytes, SHA-256, SBOM inventory, and provenance subject remained fixed.
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
- Sonatype: Software Bill of Materials (SBOM) — SBOM purpose, CycloneDX/SPDX support, analysis/export concepts, and point-in-time risk context.
- Sonatype: SBOM Best Practices — generate/store an SBOM for each application and do not use embedded vulnerability data as long-term risk state.
- Sonatype: CycloneDX Application Analysis — component identification by Package-URL, hashes, and coordinates plus dependency relationships.
- Sonatype: Repository Firewall — licensed repository-boundary policy enforcement and supported package ecosystems.
- Sonatype: Firewall Configuration for Nexus Repository — current repository-level enforcement model for recent Nexus versions.
- Sonatype: Nexus Repository Feature Matrix — Community/Pro entitlement boundaries; IQ/Firewall remain separate products/licensing.
- Sonatype: Nexus Repository System Requirements — current Java/database/runtime requirements.
- Sonatype nexus-public release 3.95.2-01 — dated Nexus baseline used by this chapter.
- CycloneDX: Specification Overview — current BOM object model, components, dependencies, hashes, and external references.
- CycloneDX 1.7 JSON Reference — current schema reference used to explain fields, not a claim that every Sonatype integration ingests every 1.7 feature.
- SLSA v1.2: Provenance — provenance as verifiable information describing where, when, and how software was produced.
- SLSA v1.2: Build Requirements — output identification by cryptographic digest and provenance-generation/integrity requirements.
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.