5.9 KiB
Here’s a simple way to make vulnerability triage almost noise‑free: combine static SBOM call‑graph 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)
-
Static stage (Sbomer + Feedser assist)
- Build SBOM (CycloneDX/SPDX).
- Generate a per‑package 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).
-
Dynamic stage (Signals + Scheduler traces)
- Ingest runtime traces (e.g., eBPF/ETW/CLR Profiler/JFR) from Scheduler‑managed jobs.
- Normalize to reachability vectors:
(component, symbol, freq, last_seen, context). - Output:
reachability_runtime.json.
-
Decision stage (Vexer)
- For each CVE → vulnerable symbols (from advisories/OSVs, delta‑sigs 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 2–4)
- 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 3–6)
-
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 6–8)
-
New
Vexer.Reachabilitypackage:- Merge static + runtime with component coordinate normalization (purl).
bool IsRuntimeReachable(component, symbol);ReachabilityScore 0..1.
4) VEX decision filter (days 8–10)
-
For each CVE advisory record (from Feedser): vulnerable symbols → lookup.
-
Decision table:
Runtime=1→ Affected (A).Static=1, Runtime=0→ NA (Not reachable) with low confidence unless policy overrides.Static=0→ NA (No code path).
-
Emit CycloneDX VEX or CSAF with justifications + evidence links.
5) UI & Ops (days 10–14)
- Stella UI: badge per CVE:
Affected / NA / Probable NA, hover shows symbols and last‑seen. - Policy Engine: org rules like “treat Probable NA as NA after 14 days of runtime with p95 traffic.”
- Notify: only alert on
AffectedorProbable NA → Atransitions.
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 Stella Ops
- Sbomer: adds call‑graph/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, delta‑sigs).
- 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 binary‑delta 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.
- Air‑gapped: ship traces via offline bundle (
*.signals.zip) and replay.
Next steps I can do now
- Draft the JSON schemas + purl mapping table.
- Add the
Vexer.Reachabilitypackage and wire the decision filter. - Provide a tiny sample repo (toy service + vulnerable lib) to demo A → Probable NA → NA as traffic evolves.
If you want, I’ll generate the initial schemas and a .NET tracer checklist so you can drop them into docs/modules/.