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** 1. **Build step** * Produce identical SBOMs in CycloneDX and SPDX. * Emit SLSA‑style provenance attestation. 2. **Scan step(s)** * OS + language deps + container layers; add **reachability proofs** where possible. * Emit one **scan attestation per tool** (don’t conflate). 3. **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). 4. **Publish** * Push image, then push SBOMs, VEX, scan attestations, policy verdicts as **OCI referrers**. 5. **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‑higher * `why`: list of net‑new reachable vulns, removed reachable vulns, policy/exception impacts * `evidenceRefs`: 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.yaml` manifest 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+json` * `application/spdx+json` * `application/vnd.in-toto+json; statement=provenance|scan|policy` * `application/vnd.stella.verdict+json` (your signed verdict/delta) **Minimal object examples (sketches)** *Attestation (scan)* ```json { "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)* ```json { "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)* ```json { "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.