4.5 KiB
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:
- Claim precedence (not_affected > fixed > affected > under_investigation)
- Break ties by Score(e)
- 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.