Files
git.stella-ops.org/docs/product-advisories/26-Dec-2026 - Weighted Confidence for VEX Sources.md

4.5 KiB
Raw Blame History

Heres a compact, practical way to rank conflicting vulnerability evidence (VEX) by freshness vs. confidence—so your system picks the best truth without handholding.


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 its signed)
  • when (issued_at, observed_at, expires_at)
  • supporting artifacts (SBOM ref, intoto link, CVE IDs, PoC link)

2) Confidence (C) and Freshness (F)

Confidence C (01) (multiply factors; cap at 1):

  • Signature strength: DSSE + Sigstore/Rekor inclusion (0.35), plus hardwarebacked 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 (01) (monotone decay):

  • F = exp(Δdays / τ) with τ tuned per source class (e.g., τ=30 vendor VEX, τ=90 NVD, τ=14 exploitactive 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 orglevel overrides (e.g., “prod must treat under_investigation as affected unless reachability=false proof present”).

5) Worked example: smallvendor Sigstore VEX vs 6monthold NVD note

  • Small vendor VEX (signed, Sigstore, reason: code path unreachable, issued 7 days ago): C ≈ signature (0.35) + smallvendor (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 StellaOps modules)

  • Where: run in scanner.webservice (per your standing rule), keep Concelier/Excitors as preserveprune pipes.

  • Storage: Postgres as SoR; Valkey as cache for score shards.

  • Inputs: CycloneDX/SPDX IDs, intoto attestations, Rekor proofs, feed timestamps.

  • Outputs:

    • Signed “verdict attestation” (OCIattached) 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 “patchaware” booster only if commit hash maps to shipped build (prove via diff or package changelog).
  • Airgapped: mirror Rekor; cache trust anchors; freeze decay at scan time but recompute at policyevaluation time.

If you want, I can drop this into a readytorun JSON/YAML policy bundle (with τ/weights defaults) and a tiny C# evaluator stub you can wire into Policy Engine → Vexer right away.