Platform Runtime Detection SBOM & SCA Container Scanning Supply Chain Solutions Fintech Engineering SaaS Engineering Regulated SaaS Integrations Pricing Docs Blog
Sign in Start Free Trial
SCA 11 min read

Runtime-SCA Correlation: How eBPF Turns a 400-CVE Backlog Into 12 Actionable Items

Runtime SCA correlation: mapping CVEs to reachable code paths in production

Static software composition analysis (SCA) has become the baseline for any engineering team that takes dependency security seriously. You point a scanner at your dependency manifest or container image, it cross-references every package version against the NVD and OSV, and it produces a list of CVEs. For a mid-size SaaS platform running 80 microservices, that list might contain 300-500 findings per sprint cycle. The problem isn't finding vulnerabilities — static SCA solved that. The problem is that most of what it finds doesn't matter for your specific runtime environment.

Runtime SCA correlation changes the filtering question. Instead of asking "which packages have known CVEs," it asks "which of those CVEs are in code that actually executes in production." The answer is reliably a much smaller set — typically 15-25% of static findings — and that smaller set is the one your engineering team should actually be working on.

Why Static SCA Produces More Noise Than Signal at Scale

The core problem with unfiltered static SCA output is that package presence doesn't imply code execution. A dependency may be included in your image because a transitive dependency declared it, but your application code never calls the vulnerable symbol. A crypto library may be present for one specific encryption operation, and the vulnerable function within that library handles a different cipher suite you don't use. An HTTP client library may have a known RCE in its proxy authentication path, but you don't use proxy authentication.

EPSS (Exploit Prediction Scoring System) scores help filter by exploitation probability — a CVSS 9.8 with EPSS 0.08 is a different risk than a CVSS 9.8 with EPSS 0.71. But EPSS is still a static signal. It tells you how likely this class of vulnerability is to be exploited across all affected systems, not whether the vulnerable code path is reachable in your specific application.

VEX (Vulnerability Exploitability eXchange) documents let a software producer assert "this CVE is not exploitable in this product because the vulnerable component is not in the execution path." VEX is the right mechanism for formal assertion, but generating accurate VEX statements requires knowing the execution path — which takes you back to needing runtime data.

This is the gap that runtime SCA correlation fills. eBPF-based runtime tracing produces a behavioral ground truth that static analysis cannot generate without taint analysis across the entire codebase, which doesn't scale to the complexity of a modern microservices dependency graph.

The Correlation Architecture: From Syscall Trace to CVE Filter

The correlation works by joining two data streams: the SBOM (which tells you what packages are present and what CVEs apply to them) and the runtime execution trace (which tells you what code actually ran).

On the runtime side, eBPF probes capture dynamic library loads via openat tracepoints on paths matching shared library patterns, and function-level execution via uprobes on the vulnerable function symbols identified from CVE metadata. The uprobe attachment approach requires knowing which binary symbols correspond to vulnerable functions — this mapping is derived from the CVE's affected version range and the package's debug symbols or ELF symbol table. For compiled languages (Go, C, Rust), symbol resolution requires the package to retain symbols, which is not always the case for optimized production builds. For JVM-based services, class loading events provide the reachability signal at class granularity rather than method granularity.

On the SBOM side, the input is a CycloneDX document per service that contains component names, versions, and associated CVE identifiers. The correlation engine joins SBOM component entries with runtime library load observations: if the library file corresponding to a component was never opened by the service process over a 30-day observation window, and the component version carries a CVE, the CVE is marked as "not loaded" — a weaker assertion than "not reachable," but already sufficient to deprioritize it relative to CVEs in components that are actively loaded.

For the subset of CVEs where the vulnerable function symbol is identifiable (typically those with specific CVE technical notes or POC code that names the function), uprobe-based observation can confirm whether the vulnerable call path was actually exercised, producing the stronger "not executed" assertion that maps directly to a VEX component_not_present or vulnerable_code_not_in_execute_path justification.

A Realistic Scenario: 80-Service SaaS Platform, Q4 Sprint

A B2B SaaS platform — 80 microservices, primarily Go and Java, running on GKE across two regions — runs a static SCA scan at the end of a sprint cycle. The scanner returns 412 findings across all services, spanning CVSS scores from 4.1 to 9.8. Their security engineer estimates 3-4 hours of manual triage to get through the critical and high severity items alone, and that's before any remediation work.

With runtime SCA correlation applied over a 30-day observation window prior to the scan, the picture changes. Of the 412 static findings: 287 are in shared libraries that were loaded at least once during the window, and 125 are in libraries with zero load events across all 80 services. Within the 287 loaded-library findings, 61 have vulnerable function symbols that were directly executed during the observation window. Those 61 are the high-priority triage set — everything else can be scheduled for the next dependency upgrade cycle without urgency.

The security engineer now has a 61-item list (down from 412) with confirmed runtime reachability, sorted by EPSS score within that set. The 3-4 hour triage session is now a 45-minute review. More importantly, the 61 findings are the ones where exploitation is not just theoretically possible but contingent on code that the application actually runs — which means the risk calculus is grounded in operational reality, not dependency-graph topology.

What This Is Not: The Boundaries of Runtime Reachability

We're not saying runtime reachability makes static SCA optional. Libraries that weren't loaded in the past 30 days may be loaded under specific conditions — peak traffic events, fallback code paths, admin endpoints that are rarely called. A "not loaded in observation window" assertion is not the same as "cannot be loaded." It's a prioritization signal, not an elimination filter.

We're also not saying that the 125 not-loaded findings should be closed. They should be acknowledged with a VEX "affected_code_cannot_be_controlled_by_adversary" or "component_not_present" justification, tracked, and revisited when the observation window refreshes. If traffic patterns change and those libraries start being loaded, the reachability status updates automatically.

The other boundary: runtime correlation requires a stable observation window. If you just deployed a new service version or changed a feature flag that activates a code path, your historical load data may not reflect the current execution profile. A one-day observation window after a major release can give you a misleading "not loaded" signal for code paths that haven't been exercised yet in the new version. Treat correlation data from the first 48 hours after a release as provisional.

Integration with VEX and Policy Enforcement

The output of the correlation engine can feed directly into VEX document generation. For each finding where the runtime trace supports a non-exploitability assertion, the system generates a CycloneDX VEX statement with the appropriate justification code and the observation metadata (window start/end, load event count, uprobe execution count if available).

These VEX documents can then flow into K8s admission policies. An OPA Gatekeeper constraint can require that any image with a critical CVE either has a remediation in the next release manifest or has a valid VEX statement with a supporting runtime trace assertion. This shifts the admission gate from "no critical CVEs allowed" (which is operationally impossible on any realistic timeline) to "no unattested critical CVEs allowed" — which is enforceable and creates a clear resolution path for engineering teams.

The combination of SBOM at build time, runtime trace from the eBPF agent, VEX generation from the correlation engine, and admission policy enforcement at the K8s gate is the full supply-chain-to-runtime security loop. Each component is doing the job it's suited for: SBOM records what's present, runtime trace records what executes, VEX formalizes the gap between them, and admission control enforces the policy at deployment time.

Calibrating the Observation Window

Thirty days is the common default for the observation window, and it's a reasonable starting point for most production services. But the right window depends on your traffic patterns. A service with a monthly billing cycle may have code paths that execute on the 1st of the month that look inactive during any other 30-day window. A service with quarterly reporting exports may have entire code subsystems that execute four times per year.

For services with known periodic execution patterns, the observation window should be extended to cover at least one full cycle — 90 days for quarterly processes. For services that are actively being refactored, the window should be reset on each major version boundary to avoid carrying forward stale load data from code paths that no longer exist. The tradeoff is between coverage confidence and data freshness: a 90-day window gives you high confidence but may include code paths from a version you no longer run. A 14-day window is current but may miss legitimate periodic code paths. Most teams land on 30 days as the practical balance for standard production services, with explicit overrides for known periodic workloads.