Files
git.stella-ops.org/docs-archived/product/advisories/09-Jan-2026 - Hybrid Reachability and VEX Integration.md
2026-01-09 18:27:46 +02:00

5.9 KiB
Raw Permalink Blame History

Heres a simple way to make vulnerability triage almost noisefree: combine static SBOM callgraph pruning with runtime reachability hints before making a VEX decision.


Why this matters (plain English)

  • Static scanners flag lots of CVEs that your app never calls.
  • Runtimes (traces, signals) know what actually executed.
  • Merging the two yields “only the CVEs that matter,” so your VEX is credible and quiet.

The hybrid model (static → dynamic → VEX)

  1. Static stage (Sbomer + Feedser assist)

    • Build SBOM (CycloneDX/SPDX).
    • Generate a perpackage call graph (library → functions → entrypoints).
    • Compute a pruned reachable set using import graphs, symbol tables, and package metadata.
    • Output: reachability_static.json (component → callable IDs).
  2. Dynamic stage (Signals + Scheduler traces)

    • Ingest runtime traces (e.g., eBPF/ETW/CLR Profiler/JFR) from Schedulermanaged jobs.
    • Normalize to reachability vectors: (component, symbol, freq, last_seen, context).
    • Output: reachability_runtime.json.
  3. Decision stage (Vexer)

    • For each CVE → vulnerable symbols (from advisories/OSVs, deltasigs if available).
    • If vulnerable symbol ∉ static set → Not Affected (NA: not reachable).
    • If ∈ static but ∉ runtime → Under Investigation / Likely NA (confidence < 1).
    • If ∈ runtime → Affected with confidence and evidence URIs.
    • Emit VEX entries with justification (not_provided_code_path, requires_config, fixed, etc.) and confidence scores.

Minimal PoC plan (2 weeks of evening work)

Goal: Let Vexer import runtime reachability vectors from Scheduler traces and use them to gate VEX verdicts.

0) Contracts (day 1)

  • Schema:

    • reachability_static.jsonl: { component, version, symbols:[...] }
    • reachability_runtime.jsonl: { component, symbol, last_seen, count, context:{pid, container, route}}
  • Evidence URIs: stella://signals/<trace-id>#symbol=<name>

1) Static extractor (days 24)

  • Sbomer plugin: emit symbol list + call graph per artifact.
  • Start with .NET: use Roslyn/IL metadata to map assembly → type → method.
  • Heuristic fallbacks for native: ELF/PE export tables.

2) Runtime collector (days 36)

  • Signals module:

    • .NET CLR Profiler or EventPipe→method enter/leave (sampling ok).
    • Map back to (assembly, type, method) + container tags.
  • Scheduler: batch traces into reachability vectors and push to Router.

3) Vexer importer (days 68)

  • New Vexer.Reachability package:

    • Merge static + runtime with component coordinate normalization (purl).
    • bool IsRuntimeReachable(component, symbol); ReachabilityScore 0..1.

4) VEX decision filter (days 810)

  • For each CVE advisory record (from Feedser): vulnerable symbols → lookup.

  • Decision table:

    • Runtime=1Affected (A).
    • Static=1, Runtime=0NA (Not reachable) with low confidence unless policy overrides.
    • Static=0NA (No code path).
  • Emit CycloneDX VEX or CSAF with justifications + evidence links.

5) UI & Ops (days 1014)

  • Stella UI: badge per CVE: Affected / NA / Probable NA, hover shows symbols and lastseen.
  • Policy Engine: org rules like “treat Probable NA as NA after 14 days of runtime with p95 traffic.”
  • Notify: only alert on Affected or Probable NA → A transitions.

Data model & code stubs (illustrative)

Vexer reachability interface

public record SymbolRef(string Purl, string Assembly, string Type, string Method);

public interface IReachabilityIndex {
    bool InStatic(SymbolRef s);
    bool InRuntime(SymbolRef s, TimeSpan since, out int count);
    double Score(SymbolRef s); // 0..1 weighted by freq/recency
}

Decision kernel

VexVerdict Decide(Cve cve, IEnumerable<SymbolRef> vulnSyms, IReachabilityIndex idx) {
    var anyRuntime = vulnSyms.Any(s => idx.InRuntime(s, TimeSpan.FromDays(14), out _));
    var anyStatic  = vulnSyms.Any(s => idx.InStatic(s));

    return anyRuntime ? VexVerdict.Affected
         : anyStatic  ? VexVerdict.ProbableNotAffected // gated by policy window
         : VexVerdict.NotAffected;
}

Evidence snippet in VEX (CycloneDX)

{
  "vulnerability": { "id": "CVE-2025-12345" },
  "analysis": {
    "state": "not_affected",
    "justification": "Vulnerable_code_not_in_execute_path",
    "response": [ "will_not_fix" ],
    "detail": "Symbols present but not executed across 14d p95 traffic",
    "evidence": [
      "stella://signals/trace-9f12#symbol=Contoso.Crypto.Rsa::Sign"
    ]
  }
}

Where it plugs into StellaOps

  • Sbomer: adds callgraph/symbol export plugin.
  • Signals: runtime tracer + compressor → reachability vectors.
  • Scheduler: tags traces by job/tenant/env; hands them to Router.
  • Vexer: new Reachability importer + decision filter.
  • Feedser: enriches CVEs with symbol hints (from OSV, deltasigs).
  • Policy Engine: organization rules for confidence windows.
  • Notify/UI/Timeline: surface verdicts, evidence, and flips over time.

Guardrails & edge cases

  • Backports: pair with your existing binarydelta signatures so “fixed but looks vulnerable” becomes NA (fixed by backport) even if symbol name matches.
  • Lazy paths & feature flags: allow context filters (route, tenant, env) before declaring NA.
  • Airgapped: ship traces via offline bundle (*.signals.zip) and replay.

Next steps I can do now

  1. Draft the JSON schemas + purl mapping table.
  2. Add the Vexer.Reachability package and wire the decision filter.
  3. Provide a tiny sample repo (toy service + vulnerable lib) to demo A → Probable NA → NA as traffic evolves.

If you want, Ill generate the initial schemas and a .NET tracer checklist so you can drop them into docs/modules/.