7.4 KiB
Here’s a simple, practical way to think about a SBOM‑first, VEX‑ready supply‑chain spine and the evidence graph + smart‑diff you can build on top of it—starting from zero and ending with reproducible, signed decisions.
SBOM‑first spine (VEX‑ready)
Goal: make the SBOM the canonical graph of “what’s inside,” then layer signed evidence (build, scans, policy) so every verdict is portable, replayable, and auditable across registries.
Core choices
-
Canonical graph: treat CycloneDX 1.6 and SPDX 3.x as first‑class. Keep both in sync; normalize component IDs (PURL/CPE), hashes, licenses, and relationships.
-
Attestations: use in‑toto + DSSE for all lifecycle facts:
- build (SLSA provenance),
- scan results (vuln, secrets, IaC, reachability),
- policy evaluation (allow/deny, risk budgets, exceptions).
-
Storage/transport: publish everything as OCI‑attached artifacts via OCI Referrers:
image:tag→ SBOM (spdx/cdx), VEX, SARIF, provenance, policy verdicts, exception notes—each a referrer with media type + signature.
-
Signatures: cosign/sigstore (or your regional crypto: eIDAS/FIPS/GOST/SM) for content‑addressed blobs.
Minimum viable workflow
-
Build step
- Produce identical SBOMs in CycloneDX and SPDX.
- Emit SLSA‑style provenance attestation.
-
Scan step(s)
- OS + language deps + container layers; add reachability proofs where possible.
- Emit one scan attestation per tool (don’t conflate).
-
Policy step
- Evaluate policies (e.g., OPA/Rego or your lattice rules) against the SBOM graph + scan evidence.
- Emit a signed policy verdict attestation (pass/fail + reasons + unknowns count).
-
Publish
- Push image, then push SBOMs, VEX, scan attestations, policy verdicts as OCI referrers.
-
Verify / consume
- Pull the image’s referrer set; verify signatures; reconstruct graph locally; replay the policy evaluation deterministically.
Data model tips
- Stable identifiers: PURLs for packages, digests for layers, Build‑ID for binaries.
- Edges:
component→dependsOn,component→vulnerability,component→evidence(attestation),component→policyClaim. - Keep time (as‑of) and source on every node/edge for replay.
Evidence graph + smart‑diff
Goal: persist an explainability graph (findings ↔ components ↔ provenance ↔ policies) and compute signed delta‑verdicts on diffs to drive precise impact analysis and quiet noise.
What to store
- Provenance: who built it, from what, when (commit, builder, materials).
- Findings: CVEs, misconfigs, secrets, license flags, each with source tool, version, rule, confidence, timestamp.
- Policies & verdicts: rule set version, inputs’ hashes, outcome, rationale.
- Reachability subgraphs: the minimal path proving exploitability (e.g., symbol → function → package → process start).
Smart‑diff algorithm (high level)
-
Compare two images (or SBOM graphs) by component identity + version + hash.
-
For each change class:
- Added/removed/changed component
- New/cleared/changed finding
- Changed reachability path
- Changed policy version/inputs
-
Re‑evaluate only affected subgraph; produce a Delta Verdict:
status: safer / risk‑equal / risk‑higherwhy: list of net‑new reachable vulns, removed reachable vulns, policy/exception impactsevidenceRefs: hashes of attestations used
-
Sign the delta verdict (DSSE) and publish it as an OCI referrer too.
UX essentials
- Artifact page shows: “Evidence Stack” (SBOM, scans, VEX, policy, provenance) with green checks for signatures.
- Smart‑diff view: left vs right image → “net‑new reachable CVEs (+3)”, “downgraded risk (‑1)” with drill‑downs to the exact path/evidence.
- Explain button: expands to show why a CVE is (not) applicable (feature flag off, code path unreachable, kernel mitigation present, etc.).
- Replay badge: “Deterministic ✅” (inputs’ hashes match; verdict reproducible).
Implementation checklist (team‑ready)
Pipelines
- Build: emit SBOM (CDX + SPDX), SLSA provenance (in‑toto/DSSE), sign all.
- Scan: OS + language + config + (optional) eBPF/runtime; one attestation per tool.
- Policy: evaluate rules → signed verdict attestation; include unknowns count.
- Publish: push all as OCI referrers; enable verification gate on pull/deploy.
Schema & IDs
- Normalize component IDs (PURL/CPE) + strong hashes; map binaries (Build‑ID → package).
- Evidence graph store: Postgres (authoritative) + cache (Valkey) for queries.
- Index by image digest; maintain as‑of snapshots for time‑travel.
Determinism
- Lock feeds, rule versions, tool versions; record all input digests.
- Provide a
replay.yamlmanifest capturing inputs → expected verdict hash.
Security & sovereignty
- Pluggable crypto: eIDAS/FIPS/GOST/SM; offline bundle export/import.
- Air‑gapped profile: Postgres‑only with documented trade‑offs.
APIs & types (suggested media types)
application/vnd.cyclonedx+jsonapplication/spdx+jsonapplication/vnd.in-toto+json; statement=provenance|scan|policyapplication/vnd.stella.verdict+json(your signed verdict/delta)
Minimal object examples (sketches)
Attestation (scan)
{
"type": "https://in-toto.io/Statement/v1",
"predicateType": "https://stella.dev/scan/v1",
"subject": [{"name": "registry/app@sha256:…", "digest": {"sha256": "..."} }],
"predicate": {
"tool": {"name": "scannerX", "version": "1.4.2"},
"inputs": {"sbom": "sha256:…", "db": "sha256:…"},
"findings": [{"id": "CVE-2025-1234", "component": "pkg:pypi/xyz@1.2.3", "severity": "HIGH"}]
}
}
Policy verdict (replayable)
{
"type": "https://in-toto.io/Statement/v1",
"predicateType": "https://stella.dev/verdict/v1",
"subject": [{"name": "registry/app@sha256:…"}],
"predicate": {
"policy": {"id": "prod.v1.7", "hash": "sha256:…"},
"inputs": {"sbom": "sha256:…", "scans": ["sha256:…","sha256:…"]},
"unknowns": 2,
"decision": "allow",
"reasons": [
"CVE-2025-1234 not reachable (path pruned)",
"License policy ok"
]
}
}
Delta verdict (smart‑diff)
{
"predicateType": "https://stella.dev/delta-verdict/v1",
"predicate": {
"from": "sha256:old", "to": "sha256:new",
"impact": "risk-higher",
"changes": {
"componentsAdded": ["pkg:apk/openssl@3.2.1-r1"],
"reachableVulnsAdded": ["CVE-2025-2222"]
},
"evidenceRefs": ["sha256:scanA", "sha256:policyV1"]
}
}
Operating rules you can adopt today
- Everything is evidence. If it influenced a decision, it’s an attestation you can sign and attach.
- Same inputs → same verdict. If not, treat it as a bug.
- Unknowns budgeted by policy. E.g., “fail prod if unknowns > 0; warn in dev.”
- Diffs decide deployments. Gate on the delta verdict, not raw CVE counts.
- Portable by default. If you move registries, your decisions move with the image via referrers.
If you want, I can turn this into starter repos (SBOM/attestation schemas, OCI‑referrer publish/verify CLI, and a smart‑diff service stub in .NET 10) so your team can plug it into your current pipelines without a big rewrite.