Here’s a compact, practical way to rank conflicting vulnerability evidence (VEX) by **freshness vs. confidence**—so your system picks the best truth without hand‑holding. --- # A scoring lattice for VEX sources **Goal:** Given multiple signals (VEX statements, advisories, bug trackers, scanner detections), compute a single verdict with a transparent score and a proof trail. ## 1) Normalize inputs → “evidence atoms” For every item, extract: * **scope** (package@version, image@digest, file hash) * **claim** (affected, not_affected, under_investigation, fixed) * **reason** (reachable?, feature flag off, vulnerable code not present, platform not impacted) * **provenance** (who said it, how it’s signed) * **when** (issued_at, observed_at, expires_at) * **supporting artifacts** (SBOM ref, in‑toto link, CVE IDs, PoC link) ## 2) Confidence (C) and Freshness (F) **Confidence C (0–1)** (multiply factors; cap at 1): * **Signature strength:** DSSE + Sigstore/Rekor inclusion (0.35), plus hardware‑backed key or org OIDC (0.15) * **Source reputation:** NVD (0.20), major distro PSIRT (0.20), upstream vendor (0.20), reputable CERT (0.15), small vendor (0.10) * **Evidence quality:** reachability proof / test (0.25), code diff linking (0.20), deterministic build link (0.15), “reason” present (0.10) * **Consensus bonus:** ≥2 independent concurring sources (+0.10) **Freshness F (0–1)** (monotone decay): * F = exp(−Δdays / τ) with τ tuned per source class (e.g., **τ=30** vendor VEX, **τ=90** NVD, **τ=14** exploit‑active feeds). * **Update reset:** new attestation with same subject resets Δdays. * **Expiry clamp:** if `now > expires_at`, set F=0. ## 3) Claim strength (S_claim) Map claim → base weight: * not_affected (0.9), fixed (0.8), affected (0.7), under_investigation (0.4) * **Reason multipliers:** reachable? (+0.15 to “affected”), “feature flag off” (+0.10 to “not_affected”), platform mismatch (+0.10), backport patch note (+0.10 if patch commit hash provided) ## 4) Overall score & lattice merge Per evidence `e`: **Score(e) = C(e) × F(e) × S_claim(e)** Then, merge in a **distributive lattice** ordered by: 1. **Claim precedence** (not_affected > fixed > affected > under_investigation) 2. Break ties by **Score(e)** 3. If competing top claims within ε (e.g., 0.05), **escalate to “disputed”** and surface both with proofs. **Policy hooks:** allow org‑level overrides (e.g., “prod must treat ‘under_investigation’ as affected unless reachability=false proof present”). ## 5) Worked example: small‑vendor Sigstore VEX vs 6‑month‑old NVD note * **Small vendor VEX (signed, Sigstore, reason: code path unreachable, issued 7 days ago):** C ≈ signature (0.35) + small‑vendor (0.10) + reason (0.10) + evidence (reachability +0.25) = ~0.70 F = exp(−7/30) ≈ 0.79 S_claim (not_affected + reason) = 0.9 + 0.10 = 1.0 (cap at 1) **Score ≈ 0.70 × 0.79 × 1.0 = 0.55** * **NVD entry (affected; no extra reasoning; last updated 180 days ago):** C ≈ NVD (0.20) = 0.20 F = exp(−180/90) ≈ 0.14 S_claim (affected) = 0.7 **Score ≈ 0.20 × 0.14 × 0.7 = 0.02** **Outcome:** vendor VEX decisively wins; lattice yields **not_affected** with linked proofs. If NVD updates tomorrow, its F jumps and the lattice may flip—deterministically. ## 6) Implementation notes (fits Stella Ops modules) * **Where:** run in **scanner.webservice** (per your standing rule), keep Concelier/Excitors as preserve‑prune pipes. * **Storage:** Postgres as SoR; Valkey as cache for score shards. * **Inputs:** CycloneDX/SPDX IDs, in‑toto attestations, Rekor proofs, feed timestamps. * **Outputs:** * **Signed “verdict attestation”** (OCI‑attached) with inputs’ hashes + chosen path in lattice. * **Delta verdicts** when any input changes (freshness decay counts as change). * **UI:** “Trust Algebra” panel showing (C,F,S_claim), decay timeline, and “why this won.” ## 7) Guardrails & ops * **Replayability:** include τ values, weights, and source catalog in the attested policy so anyone can recompute the same score. * **Backports:** add a “patch‑aware” booster only if commit hash maps to shipped build (prove via diff or package changelog). * **Air‑gapped:** mirror Rekor; cache trust anchors; freeze decay at scan time but recompute at policy‑evaluation time. --- If you want, I can drop this into a ready‑to‑run JSON/YAML policy bundle (with τ/weights defaults) and a tiny C# evaluator stub you can wire into **Policy Engine → Vexer** right away.