18 KiB
Here’s a tight, practical blueprint to turn your SBOM→VEX links into an auditable “proof spine”—using signed DSSE statements and a per‑dependency trust anchor—so every VEX verdict can be traced, verified, and replayed.
What this gives you
- A chain of evidence from each SBOM entry → analysis → VEX verdict.
- Tamper‑evident DSSE‑signed records (offline‑friendly).
- Deterministic replay: same inputs → same verdicts (great for audits/regulators).
Core objects (canonical IDs)
- ArtifactID: digest of package/container (e.g.,
sha256:…). - SBOMEntryID: stable ID for a component in an SBOM (
sbomDigest:package@version[:purl]). - EvidenceID: hash of raw evidence (scanner JSON, reachability, exploit intel).
- ReasoningID: hash of normalized reasoning (rules/lattice inputs used).
- VEXVerdictID: hash of the final VEX statement body.
- ProofBundleID: merkle root of {SBOMEntryID, EvidenceID[], ReasoningID, VEXVerdictID}.
- TrustAnchorID: per‑dependency anchor (public key + policy) used to validate the above.
Signed DSSE envelopes you’ll produce
- Evidence Statement (per evidence item)
subject: SBOMEntryIDpredicateType:evidence.stella/v1predicate: source, tool version, timestamps, EvidenceID- Signers: scanner/ingestor key
- Reasoning Statement
subject: SBOMEntryIDpredicateType:reasoning.stella/v1(your lattice/policy inputs + ReasoningID)- Signers: “Policy/Lattice Engine” key (Authority)
- VEX Verdict Statement
subject: SBOMEntryIDpredicateType: CycloneDX or CSAF VEX body + VEXVerdictID- Signers: VEXer key (or vendor key if you have it)
- Proof Spine Statement (the spine itself)
subject: SBOMEntryIDpredicateType:proofspine.stella/v1predicate: EvidenceID[], ReasoningID, VEXVerdictID, ProofBundleID- Signers: Authority key
Trust model (per‑dependency anchor)
- TrustAnchor (per package/purl): { TrustAnchorID, allowed signers (KMS refs, PKs), accepted predicateTypes, policy version, revocation list }.
- Store anchors in Authority and pin them in your graph by SBOMEntryID→TrustAnchorID.
- Optional: PQC mode (Dilithium/Falcon) for long‑term archives.
Verification pipeline (deterministic)
- Resolve SBOMEntryID → TrustAnchorID.
- Verify every DSSE envelope’s signature against the anchor’s allowed keys.
- Recompute EvidenceID/ReasoningID/VEXVerdictID from raw content; compare hashes.
- Recompute ProofBundleID (merkle root) and compare to the spine.
- Emit a Receipt: {ProofBundleID, verification log, tool digests}. Cache it.
Storage layout (Postgres + blob store)
sbom_entries(entry_id PK, bom_digest, purl, version, artifact_digest, trust_anchor_id)dsse_envelopes(env_id PK, entry_id, predicate_type, signer_keyid, body_hash, envelope_blob_ref, signed_at)spines(entry_id PK, bundle_id, evidence_ids[], reasoning_id, vex_id, anchor_id, created_at)trust_anchors(anchor_id PK, purl_pattern, allowed_keyids[], policy_ref, revoked_keys[])- Blobs (immutable): raw evidence, normalized reasoning JSON, VEX JSON, DSSE bytes.
API surface (clean and small)
POST /proofs/:entry/spine→ submit or update spine (idempotent by ProofBundleID)GET /proofs/:entry/receipt→ full verification receipt (JSON)GET /proofs/:entry/vex→ the verified VEX bodyGET /anchors/:anchor→ fetch trust anchor (for offline kits)
Normalization rules (so hashes are stable)
- Canonical JSON (UTF‑8, sorted keys, no insignificant whitespace).
- Strip volatile fields (timestamps that aren’t part of the semantic claim).
- Version your schemas:
evidence.stella/v1,reasoning.stella/v1, etc.
Signing keys & rotation
- Keep keys in your Authority module (KMS/HSM; offline export for air‑gap).
- Publish key material via an attestation feed (or Rekor‑mirror) for third‑party audit.
- Rotate by adding new allowed_keyids in the TrustAnchor; never mutate old envelopes.
CI/CD hooks
- On SBOM ingest → create/refresh SBOMEntry rows + attach TrustAnchor.
- On scan completion → produce Evidence Statements (DSSE) immediately.
- On policy evaluation → produce Reasoning + VEX, then assemble Spine.
- Gate releases on
GET /proofs/:entry/receipt== PASS.
UX (auditor‑friendly)
- Proof timeline per entry: SBOM → Evidence tiles → Reasoning → VEX → Receipt.
- One‑click “Recompute & Compare” to show deterministic replay passes.
- Red/amber flags when a signature no longer matches a TrustAnchor or a key is revoked.
Minimal dev checklist
- Implement canonicalizers (Evidence, Reasoning, VEX).
- Implement DSSE sign/verify (ECDSA + optional PQC).
- TrustAnchor registry + resolver by purl pattern.
- Merkle bundling to get ProofBundleID.
- Receipt generator + verifier.
- Postgres schema + blob GC (content‑addressed).
- CI gates + API endpoints above.
- Auditor UI: timeline + diff + receipts download.
If you want, I can drop in a ready‑to‑use JSON schema set (evidence.stella/v1, reasoning.stella/v1, proofspine.stella/v1) and sample DSSE envelopes wired to your .NET 10 stack.
Here’s a focused Stella Ops Developer Guidelines doc, specifically for the pipeline that turns SBOM data into verifiable proofs (your SBOM → Evidence → Reasoning → VEX → Proof Spine).
Feel free to paste this into your internal handbook and tweak names to match your repos/services.
Stella Ops Developer Guidelines
Turning SBOM Data Into Verifiable Proofs
1. Mental Model: What You’re Actually Building
For every component in an SBOM, Stella must be able to answer, “Why should anyone trust our VEX verdict for this dependency, today and ten years from now?”
We do that with a pipeline:
-
SBOM Ingest Raw SBOM → validated → normalized →
SBOMEntryID. -
Evidence Collection Scans, feeds, configs, reachability, etc. → canonical evidence blobs →
EvidenceID→ DSSE-signed. -
Reasoning / Policy Policy + evidence → deterministic reasoning →
ReasoningID→ DSSE-signed. -
VEX Verdict VEX statement (CycloneDX / CSAF) → canonicalized →
VEXVerdictID→ DSSE-signed. -
Proof Spine
{SBOMEntryID, EvidenceIDs[], ReasoningID, VEXVerdictID}→ merkle bundle →ProofBundleID→ DSSE-signed. -
Verification & Receipts Re-run verification →
Receiptthat proves everything above is intact and anchored to trusted keys.
Everything you do in this area should keep this spine intact and verifiable.
2. Non‑Negotiable Invariants
These are the rules you don’t break without an explicit, company-level decision:
-
Immutability of Signed Facts
- DSSE envelopes (evidence, reasoning, VEX, spines) are append‑only.
- You never edit or delete content inside a previously signed envelope.
- Corrections are made by superseding (new statement pointing at the old one).
-
Determinism
- Same
{SBOMEntryID, Evidence set, policyVersion}⇒ same{ReasoningID, VEXVerdictID, ProofBundleID}. - No non-deterministic inputs (e.g., “current time”, random IDs) in anything that affects IDs or verdicts.
- Same
-
Traceability
-
Every VEX verdict must be traceable back to:
- The precise SBOM entry
- Concrete evidence blobs
- A specific policy & reasoning snapshot
- A trust anchor defining allowed signers
-
-
Least Trust / Least Privilege
- Services only know the keys and data they need.
- Trust is always explicit: through TrustAnchors and signature verification, never “because it’s in our DB”.
-
Backwards Compatibility
- New code must continue to verify old proofs.
- New policies must not rewrite history; they produce new spines, leaving old ones intact.
3. SBOM Ingestion Guidelines
Goal: Turn arbitrary SBOMs into stable, addressable SBOMEntryIDs and safe internal models.
3.1 Inputs & Formats
-
Support at least:
- CycloneDX (JSON)
- SPDX (JSON / Tag-Value)
-
For each ingested SBOM, store:
- Raw SBOM bytes (immutable, content-addressed)
- A normalized internal representation (your own model)
3.2 IDs
-
Generate:
sbomDigest= hash(raw SBOM, canonical form)SBOMEntryID=sbomDigest + purl + version(or equivalent stable tuple)
-
SBOMEntryIDmust:- Not depend on ingestion time or database IDs.
- Be reproducible from SBOM + deterministic normalization.
3.3 Validation & Errors
-
Validate:
- Syntax (JSON, schema)
- Core semantics (package identifiers, digests, versions)
-
If invalid:
-
Reject the SBOM but record a small DSSE “failure attestation” explaining:
- Why it failed
- Which file
- Which system version
-
This still gives you a proof trail for “we tried and it failed”.
-
4. Evidence Collection Guidelines
Goal: Capture all inputs that influence the verdict in a canonical, signed form.
Typical evidence types:
- SCA / vuln scanner results
- CVE feeds & advisory data
- Reachability / call graph analysis
- Runtime context (where this component is used)
- Manual assessments (e.g., security engineer verdicts)
4.1 Evidence Canonicalization
For every evidence item:
-
Normalize to a schema like
evidence.stella/v1with fields such as:source(scanner name, feed)sourceVersion(tool version, DB version)collectionTimesbomEntryIdvulnerabilityId(if applicable)rawFinding(or pointer to it)
-
Canonical JSON rules:
- Sorted keys
- UTF‑8, no extraneous whitespace
- No volatile fields beyond what’s semantically needed (e.g., you might include
collectionTime, but then know it affects the hash and treat that consciously).
Then:
-
Compute
EvidenceID = hash(canonicalEvidenceJson). -
Wrap in DSSE:
subject:SBOMEntryIDpredicateType:evidence.stella/v1predicate: canonical evidence +EvidenceID.
-
Sign with evidence-ingestor key (per environment).
4.2 Ops Rules
- Idempotency:
Re-running the same scan with same inputs should produce the same evidence object and
EvidenceID. - Tool changes:
When tool version or configuration changes, that’s a new evidence statement with a new
EvidenceID. Do not overwrite old evidence. - Partial failure: If a scan fails, produce a minimal failure evidence record (with error details) instead of “nothing”.
5. Reasoning & Policy Engine Guidelines
Goal: Turn evidence into a defensible, replayable reasoning step with a clear policy version.
5.1 Reasoning Object
Define a canonical reasoning schema, e.g. reasoning.stella/v1:
sbomEntryIdevidenceIds[](sorted)policyVersioninputs: normalized form of all policy inputs (severity thresholds, lattice rules, etc.)intermediateFindings: optional but useful — e.g., “reachable vulns = …”
Then:
-
Canonicalize JSON and compute
ReasoningID = hash(canonicalReasoning). -
Wrap in DSSE:
subject:SBOMEntryIDpredicateType:reasoning.stella/v1predicate: canonical reasoning +ReasoningID.
-
Sign with Policy/Authority key.
5.2 Determinism
-
Reasoning functions must be pure:
- Inputs: SBOMEntryID, evidence set, policy version, configuration.
- No hidden calls to external APIs at decision time (fetch feeds earlier and record them as evidence).
-
If you need “current time” in policy:
- Treat it as explicit input and record it inside reasoning under
inputs.currentEvaluationTime.
- Treat it as explicit input and record it inside reasoning under
5.3 Policy Evolution
-
When policy changes:
- Bump
policyVersion. - New evaluations produce new
ReasoningIDand new VEX/spines. - Don’t retroactively apply new policy to old reasoning objects; generate new ones alongside.
- Bump
6. VEX Verdict Guidelines
Goal: Generate VEX statements that are strongly tied to SBOM entries and your reasoning.
6.1 Shape
-
Target standard formats:
- CycloneDX VEX
- or CSAF
-
Required linkages:
- Component reference =
SBOMEntryIDor a resolvable component identifier from your SBOM normalize layer. - Vulnerability IDs (CVE, GHSA, internal IDs).
- Status (
not_affected,affected,fixed, etc.). - Justification & impact.
- Component reference =
6.2 Canonicalization & Signing
-
Define a canonical VEX body schema (subset of the standard + internal metadata):
sbomEntryIdvulnerabilityIdstatusjustificationpolicyVersionreasoningId
-
Canonicalize JSON →
VEXVerdictID = hash(canonicalVexBody). -
DSSE-envelope:
subject:SBOMEntryIDpredicateType: e.g.cdx-vex.stella/v1predicate: canonical VEX +VEXVerdictID.
-
Sign with VEXer key or vendor key (depending on trust anchor).
6.3 External VEX
-
When importing vendor VEX:
-
Verify signature against vendor’s TrustAnchor.
-
Canonicalize to your internal schema but preserve:
- Original document
- Original signature material
-
Record “source = vendor” vs “source = stella” so auditors see origin.
-
7. Proof Spine Guidelines
Goal: Build a compact, tamper-evident “bundle” that ties everything together.
7.1 Structure
For each SBOMEntryID, gather:
EvidenceIDs[](sorted lexicographically).ReasoningID.VEXVerdictID.
Compute:
-
Merkle tree root (or deterministic hash) over:
sbomEntryId- sorted
EvidenceIDs[] ReasoningIDVEXVerdictID
-
Result is
ProofBundleID.
Create a DSSE “spine”:
-
subject:SBOMEntryID -
predicateType:proofspine.stella/v1 -
predicate:evidenceIds[]reasoningIdvexVerdictIdpolicyVersionproofBundleId
-
Sign with Authority key.
7.2 Ops Rules
-
Spine generation is idempotent:
- Same inputs → same
ProofBundleID.
- Same inputs → same
-
Never mutate existing spines; new policy or new evidence ⇒ new spine.
-
Keep a clear API contract:
GET /proofs/:entryreturns all spines, each labeled withpolicyVersionand timestamps.
8. Storage & Schema Guidelines
Goal: Keep proofs queryable forever without breaking verification.
8.1 Tables (conceptual)
sbom_entries:entry_id,bom_digest,purl,version,artifact_digest,trust_anchor_id.dsse_envelopes:env_id,entry_id,predicate_type,signer_keyid,body_hash,envelope_blob_ref,signed_at.spines:entry_id,proof_bundle_id,policy_version,evidence_ids[],reasoning_id,vex_verdict_id,anchor_id,created_at.trust_anchors:anchor_id,purl_pattern,allowed_keyids[],policy_ref,revoked_keys[].
8.2 Schema Changes
Always follow:
-
Expand
- Add new columns/tables.
- Make new code tolerant of old data.
-
Backfill
- Idempotent jobs that fill in new IDs/fields without touching old DSSE payloads.
-
Contract
- Only after all code uses the new model.
- Never drop the raw DSSE or raw SBOM blobs.
9. Verification & Receipts
Goal: Make it trivial (for you, customers, and regulators) to recheck everything.
9.1 Verification Flow
Given SBOMEntryID or ProofBundleID:
-
Fetch spine and trust anchor.
-
Verify:
- Spine DSSE signature against TrustAnchor’s allowed keys.
- VEX, reasoning, and evidence DSSE signatures.
-
Recompute:
EvidenceIDsfrom stored canonical evidence.ReasoningIDfrom reasoning.VEXVerdictIDfrom VEX body.ProofBundleIDfrom the above.
-
Compare to stored IDs.
Emit a Receipt:
proofBundleIdverifiedAtverifierVersionanchorIdresult(pass/fail, with reasons)
9.2 Offline Kit
-
Provide a minimal CLI (
stella verify) that:- Accepts a bundle export (SBOM + DSSE envelopes + anchors).
- Verifies everything without network access.
Developers must ensure:
- Export format is documented and stable.
- All fields required for verification are included.
10. Security & Key Management (for Devs)
-
Keys live in KMS/HSM, not env vars or config files.
-
Separate keysets:
dev,staging,prod- Authority vs VEXer vs Evidence Ingestor.
-
TrustAnchors:
-
Edit via Authority service only.
-
Every change:
- Requires code-reviewed change.
- Writes an audit log entry.
-
Never:
- Log private keys.
- Log full DSSE envelopes in plaintext logs (log IDs and hashes instead).
11. Observability & On‑Call Expectations
11.1 Metrics
For the SBOM→Proof pipeline, expose:
sboms_ingested_totalsbom_ingest_errors_total{reason}evidence_statements_created_totalreasoning_statements_created_totalvex_statements_created_totalproof_spines_created_totalproof_verifications_total{result}(pass/fail reason)- Latency histograms per stage (
_duration_seconds)
11.2 Logging
Include in structured logs wherever relevant:
sbomEntryIdproofBundleIdanchorIdpolicyVersionrequestId/traceId
11.3 Runbooks
You should maintain runbooks for at least:
- “Pipeline is stalled” (backlog of SBOMs, evidence, or spines).
- “Verification failures increased”.
- “Trust anchor or key issues” (rotation, revocation, misconfiguration).
- “Backfill gone wrong” (how to safely stop, resume, and audit).
12. Dev Workflow & PR Checklist (SBOM→Proof Changes Only)
When your change touches SBOM ingestion, evidence, reasoning, VEX, or proof spines, check:
-
IDs (
SBOMEntryID,EvidenceID,ReasoningID,VEXVerdictID,ProofBundleID) remain deterministic and fully specified. -
No mutation of existing DSSE envelopes or historical proof data.
-
Schema changes follow expand → backfill → contract.
-
New/updated TrustAnchors reviewed by Authority owner.
-
Unit tests cover:
- Canonicalization for any new/changed predicate.
- ID computation.
-
Integration test covers:
- SBOM → Evidence → Reasoning → VEX → Spine → Verification → Receipt.
-
Observability updated:
- New paths emit logs & metrics.
-
Rollback plan documented (especially for migrations & policy changes).
If you tell me which microservices/repos map to these stages (e.g. stella-sbom-ingest, stella-proof-authority, stella-vexer), I can turn this into a more concrete, service‑by‑service checklist with example API contracts and class/interface sketches.