19 KiB
Here’s a crisp product idea you can drop straight into Stella Ops: a VEX “proof spine”—an interactive, signed chain that shows exactly why a vuln is not exploitable, end‑to‑end.
What it is (plain speak)
- A proof spine is a linear (but zoomable) chain of facts: vuln → package → reachable symbol → guarded path → runtime context → policy verdict.
- Each segment is cryptographically signed (DSSE, in‑toto style) so users can audit who/what asserted it, with hashes for inputs/outputs.
- In the UI, the chain appears as locked graph segments. Users can expand a segment to see the evidence, but they can’t alter it without breaking the signature.
Why it’s different
- From “scanner says so” to “here’s the evidence.” This leap is what Trivy/Snyk static readouts don’t fully deliver: deterministic reachability + proof‑linked UX.
- Time‑to‑Evidence (TtE) drops: the path from alert → proof is one click, reducing back‑and‑forth with security and auditors.
- Replayable & sovereign: works offline, and every step is reproducible in air‑gapped audits.
Minimal UX spec (fast to ship)
-
Evidence Rail (left side)
- Badges per segment: SBOM, Match, Reachability, Guards, Runtime, Policy.
- Each badge shows status: ✅ verified, ⚠️ partial, ❌ missing, ⏳ pending.
-
Chain Canvas (center)
-
Segments render as locked pills connected by a line.
-
Clicking a pill opens an Evidence Drawer with:
- Inputs (hashes, versions), Tool ID, Who signed (key ID), Signature, Timestamp.
- “Reproduce” button → prefilled
stellaops scan --replay <manifest hash>.
-
-
Verdict Capsule (top‑right)
- Final VEX statement (e.g.,
not_affected: guarded-by-feature-flag) with signer, expiry, and policy that produced it.
- Final VEX statement (e.g.,
-
Audit Mode toggle
- Freezes the view, shows raw DSSE envelopes and canonical JSON of each step.
Data model (lean)
-
ProofSegmenttype:SBOM|Match|Reachability|Guard|Runtime|Policyinputs: array of{name, hash, mediaType}result: JSON blob (canonicalized)attestation: DSSE envelopetool_id,version,started_at,finished_at
-
ProofSpinevuln_id,artifact_id,segments[],verdict,spine_hash
Deterministic pipeline (dev notes)
- SBOM lock → hash the SBOM slice relevant to the package.
- Vuln match → store matcher inputs (CPE/PURL rules) and result.
- Reachability pass → static callgraph diff with symbol list; record exact rule set and graph hash.
- Guard analysis → record predicates (feature flags, config gates) and satisfiability result.
- Runtime sampling (optional) → link eBPF trace or app telemetry digest.
- Policy evaluation → lattice rule IDs + decision; emit final VEX statement.
- DSSE‑sign each step; link by previous segment hash (spine = mini‑Merkle chain).
Quick .NET 10 implementation hints
- Canonical JSON:
System.Text.Jsonwith deterministic ordering; pre‑normalize floats/timestamps. - DSSE: wrap payloads, sign with your Authority service; store
key_id,sig,alg. - Hashing: SHA‑256 of canonical result; spine hash = hash(concat of segment hashes).
- Replay manifests: emit a single
scan.replay.jsoncontaining feed versions, ruleset IDs, and all input hashes.
Tiny UI contract for Angular
-
Component:
ProofSpineComponent@Input() spine: ProofSpine- Emits:
replayRequested(spine_hash),segmentOpened(segment_id)
-
Drawer shows:
inputs,result,attestation,reproduceCTA. -
Badge colors map to verification state from backend (
verified/partial/missing/pending).
How it lands value fast
- Gives customers a credible “not exploitable” stance with audit‑ready proofs.
- Shortens investigations (SecOps, Dev, Compliance speak the same artifact).
- Creates a moat: deterministic, signed evidence chains—hard to copy with pure static lists.
If you want, I’ll draft the C# models, the DSSE signer interface, and the Angular component skeleton next. Good, let’s turn the “proof spine” into something you can actually brief to devs, UX, and auditors as a concrete capability.
I’ll structure it around: domain model, lifecycle, storage, signing & trust, UX, and dev/testing guidelines.
1. Scope the Proof Spine precisely
Core intent
A Proof Spine is the minimal signed chain of reasoning that justifies a VEX verdict for a given (artifact, vulnerability) pair. It must be:
- Deterministic: same inputs → bit-identical spine.
- Replayable: every step has enough context to re-run it.
- Verifiable: each step is DSSE-signed, chained by hashes.
- Decoupled: you can verify a spine even if Scanner/Vexer code changes later.
Non-goals (so devs don’t overextend)
- Not a general logging system.
- Not a full provenance graph (that’s for your Proof-of-Integrity Graph).
- Not a full data warehouse of all intermediate findings. It’s a curated, compressed reasoning chain.
2. Domain model: from “nice idea” to strict contract
Think in terms of three primitives:
ProofSpineProofSegmentReplayManifest
2.1 ProofSpine (aggregate root)
Per (ArtifactId, VulnerabilityId, PolicyProfileId) you have at most one latest active spine.
Key fields:
SpineId(ULID / GUID): stable ID for references and URLs.ArtifactId(image digest, repo+tag, etc.).VulnerabilityId(CVE, GHSA, etc.).PolicyProfileId(which lattice/policy produced the verdict).Segments[](ordered; see below).Verdict(affected,not_affected,fixed,under_investigation, etc.).VerdictReason(short machine code, e.g.unreachable-code,guarded-runtime-config).RootHash(hash of concatenated segment hashes).ScanRunId(link back to scan execution).CreatedAt,SupersededBySpineId?.
C# sketch:
public sealed record ProofSpine(
string SpineId,
string ArtifactId,
string VulnerabilityId,
string PolicyProfileId,
IReadOnlyList<ProofSegment> Segments,
string Verdict,
string VerdictReason,
string RootHash,
string ScanRunId,
DateTimeOffset CreatedAt,
string? SupersededBySpineId
);
2.2 ProofSegment (atomic evidence step)
Each segment represents one logical transformation:
Examples of SegmentType:
SBOM_SLICE– “Which components are relevant?”MATCH– “Which SBOM component matches this vuln feed record?”REACHABILITY– “Is the vulnerable symbol reachable in this build?”GUARD_ANALYSIS– “Is this path gated by config/feature flag?”RUNTIME_OBSERVATION– “Was this code observed at runtime?”POLICY_EVAL– “How did the lattice/policy combine evidence?”
Fields:
SegmentIdSegmentTypeIndex(0-based position in spine)Inputs(canonical JSON)Result(canonical JSON)InputHash(SHA256(canonical(Inputs)))ResultHashPrevSegmentHash(optional for first segment)Envelope(DSSE payload + signature)ToolId,ToolVersionStatus(verified,partial,invalid,unknown)
C# sketch:
public sealed record ProofSegment(
string SegmentId,
string SegmentType,
int Index,
string InputHash,
string ResultHash,
string? PrevSegmentHash,
DsseEnvelope Envelope,
string ToolId,
string ToolVersion,
string Status
);
public sealed record DsseEnvelope(
string PayloadType,
string PayloadBase64,
IReadOnlyList<DsseSignature> Signatures
);
public sealed record DsseSignature(
string KeyId,
string SigBase64
);
2.3 ReplayManifest (reproducibility anchor)
A ReplayManifest is emitted per scan run and referenced by multiple spines:
ReplayManifestIdFeeds(names + versions + digests)Rulesets(reachability rules version, lattice policy version)Tools(scanner, sbomer, vexer versions)Environment(OS, arch, container image digest where the scan ran)
This is what your CLI will take:
stellaops scan --replay <ReplayManifestId> --artifact <ArtifactId> --vuln <VulnerabilityId>
3. Lifecycle: where the spine is built in Stella Ops
3.1 Producer components
The following services contribute segments:
Sbomer→SBOM_SLICEScanner→MATCH, maybeRUNTIME_OBSERVATIONif it integrates runtime tracesReachability EngineinsideScanner/ dedicated module →REACHABILITYGuard Analyzer(config/feature flag evaluator) →GUARD_ANALYSISVexer/Excititor→POLICY_EVAL, final verdictAuthority→ optional cross-signing / endorsement segment (TRUST_ASSERTION)
Important: each microservice emits its own segments, not a full spine. A small orchestrator (inside Vexer or a dedicated ProofSpineBuilder) collects, orders, and chains them.
3.2 Build sequence
Example for a “not affected due to guard” verdict:
-
SbomerproducesSBOM_SLICEsegment for(Artifact, Vuln)and DSSE-signs it. -
Scannertakes slice, producesMATCHsegment (component X -> vuln Y). -
ReachabilityproducesREACHABILITYsegment (symbol reachable or not). -
Guard AnalyzerproducesGUARD_ANALYSISsegment (path is gated byfeature_x_enabled=falseunder current policy context). -
Vexerevaluates lattice, producesPOLICY_EVALsegment with final VEX statementnot_affected. -
ProofSpineBuilder:- Sorts segments by predetermined order.
- Chains
PrevSegmentHash. - Computes
RootHash. - Stores
ProofSpinein canonical store and exposes it via API/GraphQL.
4. Storage & PostgreSQL patterns
You are moving more to Postgres for canonical data, so think:
4.1 Tables (conceptual)
proof_spines:
spine_id(PK)artifact_idvuln_idpolicy_profile_idverdictverdict_reasonroot_hashscan_run_idcreated_atsuperseded_by_spine_id(nullable)segment_count
Indexes:
(artifact_id, vuln_id, policy_profile_id)(scan_run_id)(root_hash)
proof_segments:
segment_id(PK)spine_id(FK)idxsegment_typeinput_hashresult_hashprev_segment_hashenvelope(bytea or text)tool_idtool_versionstatuscreated_at
Optional proof_segment_payloads if you want fast JSONB search on inputs / result:
segment_id(PK) FKinputs_jsonbresult_jsonb
Guidelines:
- Use append-only semantics: never mutate segments; supersede by new spine.
- Partition
proof_spinesandproof_segmentsby time orscan_run_idif volume is large. - Keep envelopes as raw bytes; only parse/validate on demand or asynchronously for indexing.
5. Signing, keys, and trust model
5.1 Signers
At minimum:
- One keypair per service (Sbomer, Scanner, Reachability, Vexer).
- Optional: vendor keys for imported spines/segments.
Key management:
-
Keys and key IDs are owned by
Authorityservice. -
Services obtain signing keys via short-lived tokens or integrate with HSM/Key vault under Authority control.
-
Key rotation:
- Keys have validity intervals.
- Spines keep
KeyIdin each DSSE signature. - Authority maintains a trust table: which keys are trusted for which
SegmentTypeand time window.
5.2 Verification flow
When UI loads a spine:
-
Fetch
ProofSpine+ProofSegments. -
For each segment:
- Verify DSSE signature via Authority API.
- Validate
PrevSegmentHashintegrity.
-
Compute
RootHashand check against storedRootHash. -
Expose per-segment
statusto UI:verified,untrusted-key,signature-failed,hash-mismatch.
This drives the badge colors in the UX.
6. UX: from “rail + pills” to full flows
Think of three primary UX contexts:
- Vulnerability detail → “Explain why not affected”
- Audit view → “Show me all evidence behind this VEX statement”
- Developer triage → “Where exactly did the reasoning go conservative?”
6.1 Spine view patterns
For each (artifact, vuln):
-
Top summary bar
-
Verdict pill:
Not affected (guarded by runtime config) -
Confidence / verification status: e.g.
Proof verified,Partial proof. -
Links:
- “Download Proof Spine” (JSON/DSSE bundle).
- “Replay this analysis” (CLI snippet).
-
-
Spine stepper
-
Horizontal list of segments (SBOM → Match → Reachability → Guard → Policy).
-
Each segment displays:
- Type
- Service name
- Status (icon + color)
-
On click: open side drawer.
-
-
Side drawer (segment detail)
-
Who:ToolId,ToolVersion,KeyId. -
When: timestamps. -
Inputs:- Pretty-printed subset with “Show canonical JSON” toggle.
-
Result:- Human-oriented short explanation + raw JSON view.
-
Attestation:- Signature summary:
Signature verified / Key untrusted / Invalid. PrevSegmentHash&ResultHash(shortened with copy icons).
- Signature summary:
-
“Run this step in isolation” button if you support it (nice-to-have).
-
6.2 Time-to-Evidence (TtE) integration
You already asked for guidelines on “Tracking UX Health with Time-to-Evidence”.
Use the spine as the data source:
-
Measure
TtEas:time_from_alert_opened_to_first_spine_viewORtime_from_alert_opened_to_verdict_understood.
-
Instrument events:
spine_opened,segment_opened,segment_scrolled_to_end,replay_clicked.
-
Use this to spot UX bottlenecks:
- Too many irrelevant segments.
- Missing human explanations.
- Overly verbose JSON.
6.3 Multiple paths and partial evidence
You might have:
- Static reachability: says “unreachable”.
- Runtime traces: not collected.
- Policy: chooses conservative path.
UI guidelines:
-
Allow small branching visualization if you ever model alternative reasoning paths, but for v1:
- Treat missing segments as explicit
pending/unknown. - Show them as grey pills: “Runtime observation: not available”.
- Treat missing segments as explicit
7. Replay & offline/air-gap story
For air-gapped Stella Ops this is one of your moats.
7.1 Manifest shape
ReplayManifest (JSON, canonicalized):
-
manifest_id -
generated_at -
tools:{ "id": "Scanner", "version": "10.1.3", "image_digest": "..." }- etc.
-
feeds:{ "name": "nvd", "version": "2025-11-30T00:00:00Z", "hash": "..." }
-
policies:{ "policy_profile_id": "default-eu", "version": "3.4.0", "hash": "..." }
CLI contract:
stellaops scan \
--replay-manifest <id-or-file> \
--artifact <image-digest> \
--vuln <cve> \
--explain
Replay guarantees:
-
If the artifact and feeds are still available, replay reproduces:
- identical segments,
- identical
RootHash, - identical verdict.
-
If anything changed:
- CLI clearly marks divergence: “Recomputed proof differs from stored spine (hash mismatch).”
7.2 Offline bundle integration
Your offline update kit should:
- Ship manifests alongside feed bundles.
- Keep a small index “manifest_id → bundle file”.
- Allow customers to verify that a spine produced 6 months ago used feed version X that they still have in archive.
8. Performance, dedup, and scaling
8.1 Dedup segments
Many artifacts share partial reasoning, e.g.:
- Same base image SBOM slice.
- Same reachability result for a shared library.
You have options:
-
Simple v1: keep segments embedded in spines. Optimize later.
-
Advanced: deduplicate by
ResultHash+SegmentType+ToolId:- Store unique “segment payloads” in a table keyed by that combination.
ProofSegmentthen references payload via foreign key.
Guideline for now: instruct devs to design with possible dedup in mind (segment payloads should be referable).
8.2 Retention strategy
-
Keep full spines for:
- Recent scans (e.g., last 90 days) for triage.
- Any spines that were exported to auditors or regulators.
-
For older scans:
- Option A: keep only
POLICY_EVAL+RootHash+ short summary. - Option B: archive full spines to object storage (S3/minio) keyed by
RootHash.
- Option A: keep only
9. Security & multi-tenant boundaries
Stella Ops will likely serve many customers / environments.
Guidelines:
-
SpineIdis globally unique, but all queries must be scope-checked by:TenantIdEnvironmentId
-
Authority verifies not only signatures, but also key scopes:
- Key X is only allowed to sign for Tenant T / Environment E, or for system-wide tools.
-
Never leak:
- File paths,
- Internal IPs,
- Customer-specific configs, in the human-friendly explanation. Those can stay in the canonical JSON, which is exposed only in advanced / audit mode.
10. Developer & tester guidelines
10.1 For implementors (C# / .NET 10)
-
Use a single deterministic JSON serializer (e.g. wrapper around
System.Text.Json) with:- Stable property order.
- Standardized timestamp format (UTC ISO 8601).
- Explicit numeric formats (no locale-dependent decimals).
-
Before signing:
- Canonicalize JSON.
- Hash bytes directly.
-
Never change canonicalization semantics in a minor version. If you must, bump a major version and record it in
ReplayManifest.
10.2 For test engineers
Build a curated suite of fixture scenarios:
-
“Straightforward not affected”:
- Unreachable symbol, no runtime data, conservative policy: still
not_affecteddue to unreachable.
- Unreachable symbol, no runtime data, conservative policy: still
-
“Guarded at runtime”:
- Reachable symbol, but guard based on config →
not_affected.
- Reachable symbol, but guard based on config →
-
“Missing segment”:
- Remove
REACHABILITYsegment → policy should downgrade toaffectedorunder_investigation.
- Remove
-
“Signature tampering”:
- Flip a byte in one DSSE payload → UI must show
invalidand mark entire spine as compromised.
- Flip a byte in one DSSE payload → UI must show
-
“Key revocation”:
- Mark a key untrusted → segments signed with it become
untrusted-keyand spine is partially verified.
- Mark a key untrusted → segments signed with it become
Provide golden JSON for:
ProofSpineobject.- Each
ProofSegmentenvelope. - Expected
RootHash. - Expected UI status per segment.
11. How this ties into your moats
This Proof Spine is not just “nice UX”:
-
It is the concrete substrate for:
- Trust Algebra Studio (the lattice engine acts on segments and outputs
POLICY_EVALsegments). - Proof-Market Ledger (publish
RootHash+ minimal metadata). - Deterministic, replayable scans (spine + manifest).
- Trust Algebra Studio (the lattice engine acts on segments and outputs
-
Competitors can show “reasons”, but you are explicitly providing:
- Signed, chain-of-evidence reasoning,
- With deterministic replay,
- Packaged for regulators and procurement.
If you want, next step I can draft:
- A proto/JSON schema for
ProofSpinebundles for export/import. - A minimal set of REST/GraphQL endpoints for querying spines from UI and external auditors.