Safeguard.sh Documentation Center

Vulnerability Prioritization (Reachability Analysis)

How Safeguard uses call-graph reachability, EPSS, KEV, and runtime context to cut 60-80% of CVE noise so teams patch what actually matters.

Vulnerability Prioritization

A typical enterprise application has 200–500 direct and transitive dependencies, and 5–15% of them have known CVEs at any moment. Most are unreachable in the context of how the application actually uses the library. Reachability analysis tells you which CVEs are actually relevant, so your team patches the ones that matter.

Safeguard's prioritization engine combines four signals: reachability, exploitability (EPSS + KEV), runtime context, and business context. The result is a single ranked backlog that typically shrinks open findings by 60-80% without missing real risk.

The Four Signals

1. Reachability

A CVE is "reachable" when your code actually calls the vulnerable function or triggers the vulnerable code path.

Safeguard performs three kinds of reachability analysis:

  • Static reachability — builds a call graph from your source, lockfile, and bytecode, and checks whether any path leads to the vulnerable sink.
  • Dynamic reachability — at runtime, the collector records which modules and functions are actually loaded and invoked in your production workloads. Flags never-executed vulnerable paths as cold.
  • Configuration reachability — parses framework configuration (e.g., Spring bean definitions, Next.js route files) to determine if code is wired in at all.

A vulnerability is classified as:

  • Reachable — a real call path exists and is instantiated at runtime.
  • Conditionally reachable — path exists but requires a feature flag, route, or configuration not currently enabled.
  • Unreachable — no call path exists in your compiled code.
  • Unknown — the analyzer couldn't decide (e.g., dynamic dispatch, reflection, native code). Marked conservatively as potentially reachable.

2. Exploitability — EPSS and KEV

  • EPSS (Exploit Prediction Scoring System) estimates the probability of exploitation within 30 days. Safeguard ingests the daily EPSS feed.
  • KEV (CISA Known Exploited Vulnerabilities) lists vulnerabilities observed being exploited in the wild. If a CVE is on KEV, exploitation is not hypothetical.
  • Weaponization signal — Safeguard tracks public PoC availability (exploit-db, GitHub PoC repos, Metasploit modules) as an additional exploit-availability signal.

3. Runtime Context

A vulnerability in code that only runs in a dev Docker compose file is not the same as a vulnerability in the production API. The runtime collector labels each asset with:

  • env: production, staging, dev, sandbox.
  • exposure: internet-facing, internal, air-gapped.
  • data_class: customer PII, PHI, PCI, public.
  • criticality: inferred from asset ownership and business tags.

These labels feed into the prioritization score.

4. Business Context

Via policy you can declare which assets are regulated (PCI, HIPAA, FedRAMP), which handle customer data, and which are on the critical path for revenue. Findings on those assets rank higher.

The Unified Priority Score

Each finding gets a Priority Score from 0-100:

priority = f(
  base_severity,          // CVSS 3.1 base score
  epss_probability,       // 0.0 - 1.0
  kev_listed,             // boolean
  poc_available,          // boolean
  reachability_class,     // reachable | conditional | unreachable | unknown
  env,                    // production > staging > dev
  exposure,               // internet-facing > internal
  data_sensitivity,       // regulated > public
  business_criticality    // policy-defined
)

The exact weights are configurable per tenant (Settings → Prioritization). Defaults map to the intuitive ordering:

  1. Reachable + KEV-listed + production → Priority 90-100 (P0).
  2. Reachable + EPSS > 0.5 + production → Priority 75-89 (P1).
  3. Reachable + high CVSS + staging → Priority 50-74 (P2).
  4. Unreachable + any severity → Priority 0-30 (backlog; auto-suppressed in some views).

Typical Noise Reduction

Based on aggregate data across customer tenants:

Source of reduction% findings removed from "urgent" queue
Reachability filtering60-80%
EPSS < 0.05additional 5-15%
Asset-class filtering (dev/sandbox)additional 5-10%
Combined deduplicationadditional 5-10%

A typical mid-sized team sees an open urgent queue of 20-60 CVEs per week instead of 200-500.

Language and Ecosystem Support

Reachability analysis quality varies by language. Current support:

Language / EcosystemStatic reachabilityDynamic reachabilityNotes
Java / Kotlin / Scala (JVM)GAGABytecode-level call graph. High fidelity.
PythonGAGAImport- and AST-based; handles virtualenv layouts.
JavaScript / TypeScriptGAGAHandles CommonJS, ESM, and webpack bundles.
GoGAGAModule-aware; ssa-based call graph.
RustGAGAcargo metadata + LLVM IR path extraction.
C / C++GAGAclang-based call graph for reachability on common package managers.
RubyPreviewGAMethod resolution conservative for metaprogramming.
PHPPreviewGAComposer-aware; Symfony / Laravel-aware.
C# / .NETGAGARoslyn-based; NuGet-aware.
Swift / Objective-CPreviewPreviewFramework-aware.

How to View It

Vulnerabilities Tab (ESSCM)

The vulnerabilities table has a Priority column and a Reachability column. Default sort is Priority descending.

Common filters:

  • reachable:true — only reachable findings.
  • kev:true — only KEV-listed findings.
  • epss:>0.3 — only probability above 30%.
  • env:production reachable:true kev:true — the P0 backlog.

Griffin Natural Language

Griffin understands the prioritization surface directly:

"Show my P0 findings in production that are reachable."
"Which of last week's new CVEs are reachable but not yet on KEV?"
"Summarize the top 10 priorities for the payments service."

Dashboards

The Prioritization dashboard shows:

  • Total finding volume vs. urgent queue (trend over time).
  • Time-to-remediate by priority band.
  • Drop rate for each signal (how much each layer filtered out).
  • Backlog aging — how long findings sit before being patched, suppressed, or excepted.

Integrating With Policy

Reachability feeds directly into guardrails. A production-admission policy might say:

- id: no-reachable-kev-in-prod
  condition: |
    env == "production"
    AND any(vulnerabilities,
      reachability_class in ["reachable", "conditional"]
      AND kev == true
    )
  effect: BLOCK

See Guardrails & Enforcement for the full policy language.

When Reachability Doesn't Help

Reachability is not a silver bullet. It's weakest when:

  • Code executes dynamically via reflection, eval, or unsafe dispatch. Safeguard marks these as unknown and does not suppress them.
  • Vulnerabilities are data-driven (e.g., accepts a malicious input even though the function is unreachable by static analysis).
  • The vulnerable code path runs only under specific configurations that the analyzer cannot enumerate.

For these cases, rely on the other signals (KEV, EPSS, runtime observation) and conservative defaults.

Auto-Fix and Griffin Integration

Once a finding is prioritized, Griffin can:

  • Propose a minimum-risk upgrade that resolves the vulnerability.
  • Generate a safe-downgrade when a patched version doesn't yet exist.
  • Explain why a finding was de-prioritized (essential for exception reviews).
  • Auto-open PRs for the top-N priorities on a schedule.

See Auto-Fix.

API

Query findings sorted by priority:

safeguard findings list \
  --asset my-api \
  --env production \
  --reachable true \
  --min-priority 75

Or via API:

curl -H "Authorization: Bearer $SG_TOKEN" \
  "https://api.safeguard.sh/v1/findings?asset=my-api&reachable=true&min_priority=75"

On this page