Join our Newsletter — 33% off our NHI Course
Home› Guides› Reachability Analysis Guide: Prioritising Vulnerabilities That Can Actually…
Guide Vulnerability Management

Reachability Analysis Guide: Prioritising Vulnerabilities That Can Actually Be Reached

PDF for newsletter subscribers · free to read online

← All guides
By Lalit Choda, NHI Mgmt Group Updated 24 October 2026 15 min read
On this page

A vulnerable component being present is not the same as a vulnerability an attacker can reach, and treating the two as equal buries teams in findings they cannot exploit and cannot clear. Reachability analysis asks whether the vulnerable code is called by your application, whether it actually executes in production, and whether an attacker can get network traffic or input to it. Done well, it removes a large share of dependency noise and gives you evidence to publish VEX statements; done badly, it hides exploitable flaws behind a "not reachable" label. This guide covers code (SCA) reachability with function-level call graphs, runtime reachability, network reachability, how to record the results in CSAF VEX, OpenVEX and CycloneDX, and where each method produces false negatives. It is for application security engineers, product security teams and vulnerability management leads.

Key takeaways

  • Reachability is three questions, not one. Is the vulnerable function called (code), does it run in the deployed workload (runtime), and can an attacker reach the entry point (network and input)? Each needs different evidence.
  • "Not reachable" is a claim that must be defended. Static analysis misses reflection, dynamic dispatch and unsafe code; Go's govulncheck documents that calls through reflect are not visible and that unsafe may cause false negatives.
  • Symbol data is the limiting factor. Function-level reachability only works when the advisory names the vulnerable functions. The OSV schema has no core field for them; Go and Rust carry them in ecosystem_specific.
  • Reachability lowers priority; it never overrides exploitation evidence. A KEV entry on an internet-facing asset stays urgent until a tested control or fix is in place.
  • Publish results as VEX, with justifications. CISA's minimum requirements, OpenVEX and the CSAF VEX profile all use vulnerable_code_not_in_execute_path for "present but not reachable", and they require an impact statement or justification for not_affected.

What reachability analysis is and is not

Software composition analysis (SCA) matches the components and versions in your manifests, lock files, images or SBOMs against advisories. That tells you a vulnerable version is present. It does not tell you whether the flawed function is ever called, whether the code is loaded at runtime, or whether hostile input can arrive there. Reachability analysis adds that context so the queue reflects exploitability rather than inventory.

  • It is a prioritisation input: reachability changes the likelihood term in the risk model. It sits alongside KEV, EPSS, CVSS and asset criticality, as described in the Vulnerability Prioritisation Guide. FIRST's EPSS user guide likewise asks defenders to consider how accessible the vulnerable asset is to attackers before deciding to remediate or delay.
  • It is not a reason to stop upgrading: unreachable vulnerable code can become reachable with the next feature, configuration change or refactor. Reachability decides the order of work, not whether the component is ever fixed.
  • It is not one product category: SCA tools, language toolchains, runtime agents, cloud network analysers and attack path tools all answer parts of the question. Most organisations need at least two of them.

The three layers of reachability

LayerQuestionEvidenceTypical toolingMain blind spot
Package or importIs the vulnerable package imported by first-party code, or only declared?Import graph from source or buildSCA with dependency graphCoarse: an imported package may never call the vulnerable function
Function (static code)Does any path in the call graph lead from your code to the vulnerable symbol?Call graph plus advisory symbol dataLanguage toolchains (for example govulncheck), SCA reachability enginesReflection, dynamic dispatch, plugins, code loaded at runtime
RuntimeIs the vulnerable library loaded, or the function executed, in the deployed workload?Agent or kernel telemetry of loaded packages, classes or executed functionsRuntime application or container agents, eBPF-based sensorsCode paths not exercised during the observation window
Network and inputCan attacker-controlled traffic or data reach the entry point that leads to the code?Routing, security groups, ACLs, load balancer and ingress configuration; input sourcesCloud reachability analysers, attack surface management, attack path toolsIndirect paths (SSRF, queues, file uploads, partner integrations)

The layers combine. A function reachable in the call graph, loaded at runtime, inside a service that the internet can reach, is the strongest case for urgency. A function absent from the call graph in a batch job with no inbound network path is the strongest case for deferral, provided the evidence is recorded.

Code reachability: function-level call graphs

Function-level reachability builds a call graph of your application and its dependencies, then checks whether any path leads from an entry point in your code to a symbol the advisory names as vulnerable. Go's tooling is the clearest public example. The Go vulnerability database stores reports in OSV format with package and symbol information, which the Go team says it reviews for accuracy, and govulncheck "only surfaces vulnerabilities that actually affect you, based on which functions in your code are transitively calling vulnerable functions".

What the analysis needs

  • Symbol-level advisories: the OSV schema has no core field for affected functions. Ecosystems put them in affected[].ecosystem_specific; the schema notes that Go includes affected functions there, and its Rust example lists functions such as http::header::HeaderMap::reserve. Where an advisory carries no symbols, a tool can only report at package level or must rely on its vendor's own curation.
  • Source or rich build artefacts: govulncheck's binary mode lacks detailed call information, cannot show call stacks and may report unreachable code as a false positive. Binaries built with Go versions before 1.18 get standard library reports only, and if symbol information cannot be extracted, every module the binary depends on is reported.
  • The full dependency graph: transitive dependencies are where most vulnerable code lives. Analyse lock files or the resolved build graph, not just direct manifest entries. See the SBOM Guide.
  • Entry points that match deployment: tests, examples and command-line tools inside a repository can create call paths that never ship. Scope the analysis to the deployed binaries or services.

Where static analysis gets it wrong

CauseEffectError typeMitigation
Reflection (Go reflect, Java reflection, Python getattr and dynamic import)Calls are invisible to the call graph; govulncheck documents that such vulnerable code is not reported in source modeFalse negativeTreat reflection-heavy frameworks as reachable by default; confirm with runtime data
Unsafe or native code (Go unsafe, JNI, cgo, native extensions)Analysis loses track of memory and calls; govulncheck notes unsafe may cause false negativesFalse negativeDo not mark native-code paths not_affected on static evidence alone
Interfaces, function pointers, callbacksAnalysed conservatively, giving extra or inaccurate call stacksFalse positiveReview the reported call stack before dismissing or escalating
Plugins, dependency injection, serialisation gadgets, configuration-driven loadingCode chosen at runtime by configuration or inputFalse negativeModel the configured plugins; treat deserialisation of untrusted input as reachable
Missing or wrong advisory symbolsWrong function checked, or package-level fallbackEitherRecord whether the result was symbol-level or package-level

Runtime reachability

Runtime reachability observes the running workload: which packages and libraries are loaded, and in more detailed implementations which classes or functions execute. It catches what static analysis misses (reflection, plugins, dynamically loaded code) and discounts components that ship in an image but are never loaded, such as unused operating system packages.

  • Loaded versus executed: "the library is loaded" is weaker evidence than "the vulnerable function executed". Record which level a tool reports.
  • Observation window: absence of execution only means the path did not run while you were watching. Month-end jobs, failover paths, error handlers and rarely used API endpoints may not run for weeks. Set a minimum window and require representative traffic before treating "not executed" as evidence.
  • Coverage of the estate: runtime data exists only where an agent or sensor runs. Unmonitored hosts, serverless functions and third-party hosted services fall back to static results.
  • Production parity: observations from staging with synthetic traffic are useful for development but should not be the evidence behind a production not_affected claim.
  • Operational cost and privilege: runtime sensors often run with high privilege in the kernel or container runtime. Treat their identities and update channels as privileged infrastructure. See the Container Runtime Security Guide.

Network and input reachability

Code reachability tells you the vulnerable function can run; network reachability tells you whether an attacker can send it input. Cloud providers offer configuration analysers for this. AWS VPC Reachability Analyzer, for example, is a configuration analysis tool that does not send packets: you name a source and destination, and it returns the hop-by-hop path or the component that blocks it, such as a security group, network ACL, route table or load balancer. Paths can be defined as code with the CloudFormation resources AWS::EC2::NetworkInsightsPath and AWS::EC2::NetworkInsightsAnalysis, so reachability intent can be checked on every change.

  • Start from the internet edge: internet-facing services carry the highest exposure class and are the targets of MITRE ATT&CK T1190 Exploit Public-Facing Application. In CitrixBleed 2, Shadowserver still counted about 4,700 unpatched internet-facing NetScaler instances a month after the fix. For those, reachability is not in doubt. See the Attack Surface Management Guide.
  • Model indirect input paths: network isolation does not protect code that processes data from a queue, an uploaded file, an email or a partner API. If attacker-influenced data reaches the function, it is reachable.
  • Remember internal reach: a service unreachable from the internet may still be reachable from user networks or any compromised workload in the same segment. Record the exposure class (internet, user network, internal service, isolated) rather than a yes or no. See the Network Segmentation Guide.
  • Configuration drifts: a security group change can make an isolated service reachable in minutes. Re-run network reachability on change and re-tier findings automatically.

Where identity fits

Network reachability also applies to the credentials a workload can reach. In the Capital One breach, a misconfigured web application firewall could be made to query its own instance metadata service, which returned the attached role's temporary credentials; that role could list and read many S3 buckets. The flaw mattered because the metadata endpoint was reachable from the vulnerable component and the role was over-privileged.

  • Add credential reachability to the assessment: for any reachable vulnerability that allows request forgery, file read or code execution, list the secrets, tokens and roles the process can reach (metadata services, mounted service account tokens, environment variables, local key files).
  • Reduce what is reachable: require IMDSv2, block metadata access from containers that do not need it, and scope workload roles to their function. See the Kubernetes Service Account Token Guide.

Recording results as VEX

Vulnerability Exploitability eXchange (VEX) lets a supplier, or an internal product team, state whether a product is affected by a vulnerability and why. Reachability results are the most common evidence behind a not_affected statement. CISA notes that issuing VEX is a business decision for suppliers; for internal use, it is the cleanest way to stop the same unreachable finding being triaged repeatedly.

ElementCISA minimum requirements (April 2023)OpenVEXCSAF 2.0 VEX profileCycloneDX
Status valuesnot_affected, affected, fixed, under_investigationnot_affected, affected, fixed, under_investigationproduct_status: known_not_affected, known_affected, fixed, under_investigationanalysis state: not_affected, exploitable, resolved, resolved_with_pedigree, in_triage, false_positive
"Present but not reachable"Vulnerable_code_not_in_execute_pathvulnerable_code_not_in_execute_pathflag label vulnerable_code_not_in_execute_pathjustification code_not_reachable
Other not-affected reasonsComponent_not_present, Vulnerable_code_not_present, Vulnerable_code_cannot_be_controlled_by_adversary, Inline_mitigations_already_existSame five labelsSame five labelscode_not_present, requires_configuration, requires_dependency, requires_environment, protected_by_compiler, protected_at_runtime, protected_at_perimeter, protected_by_mitigating_control
Rule for not affectedImpact statement required if no justificationJustification or impact_statement requiredMachine-readable flag or a threats entry of category impactJustification and detail fields
Rule for affectedAction statement requiredaction_statement should describe remediationremediations entry required (no_fix_planned and none_available permitted)response and recommendation fields
VersioningDocument and statement versions incremented on any changeDocument version incremented on change; timestamps cascade from document to statementdocument/category csaf_vex; tracking metadataWithin the BOM or a separate VEX BOM
  • Use the precise justification: CSAF defines vulnerable_code_not_in_execute_path as code "not reachable through the execution of the code, including non-anticipated states of the product". That is a strong claim; static analysis alone rarely covers non-anticipated states, so record the evidence in the impact statement.
  • Prefer under_investigation to a guess: when reachability results are package-level only or contradicted by runtime data, publish under_investigation and revisit.
  • Tie each statement to a product version: a not_affected statement is true for a specific build. Re-evaluate on every release, because a code change can create the call path.
  • Sign and distribute alongside the SBOM: CISA recommends cryptographic signing. Consumers should match VEX product identifiers to their SBOM components before suppressing anything.

Implementing reachability analysis

  1. Inventory first: generate SBOMs from builds and images for the applications in scope, including transitive dependencies, so every finding maps to a product version and owner.
  2. Enable static reachability in report-only mode: run function-level analysis in CI for one language with good symbol data (Go is the easiest start) and compare its results with the existing SCA queue for two or three release cycles. Do not suppress anything yet.
  3. Classify results by evidence level: tag each finding as symbol-level reachable, symbol-level not reachable, package-level only or unknown. Only symbol-level not reachable is eligible for deprioritisation.
  4. Add runtime data for production services: enable loaded-package or executed-function telemetry on a pilot set of services, set a minimum observation window, and reconcile with static results. Where they disagree, runtime execution wins over static "not reachable".
  5. Join network exposure: attach exposure class from attack surface and cloud reachability data to each service, and re-tier when it changes.
  6. Write the policy rules: define which combinations lower priority (for example, symbol-level not reachable and not loaded at runtime and not internet-facing) and which never do (KEV entries on internet-facing services, deserialisation of untrusted input, reflection-heavy frameworks).
  7. Publish VEX for suppressions: every deprioritised finding gets a VEX statement with justification, evidence and product version, so the decision is auditable and reusable by downstream consumers.
  8. Roll back by removing suppressions, not data: keep raw SCA findings. If a false negative is found, revoke the affected VEX statements, return findings to the queue and fix the rule that allowed them.

Detection and measurement

  • Noise reduction with evidence: share of SCA findings deprioritised with a recorded VEX justification. Report it with the evidence level, not as a bare percentage.
  • Reachable backlog age: median age of findings that are symbol-level reachable on internet-facing services. This should fall as attention moves to them.
  • Static and runtime disagreement rate: percentage of statically "not reachable" findings observed executing at runtime. A rising rate means the static engine is missing patterns in your codebase.
  • VEX reversals: count of not_affected statements later changed to affected. Every reversal needs a root cause.
  • Coverage: share of production services with symbol-level static analysis and with runtime telemetry.
  • Re-evaluation latency: time from a code release or network change to updated reachability results.

Framework mapping

ActivityNIST SP 800-53 Rev. 5NIST CSF 2.0Threat addressed
Dependency vulnerability identification and analysisRA-5 Vulnerability Monitoring and Scanning, SA-11 Developer Testing and EvaluationID.RA-01T1195.001 Compromise Software Dependencies and Development Tools
Prioritising by exploitability and impactRA-3 Risk Assessment, SI-2 Flaw RemediationID.RA-05T1190 Exploit Public-Facing Application
Component provenance, SBOM and VEXSR-3 Supply Chain Controls and Processes, SR-4 ProvenanceID.RA-01T1195.001
Removing unused componentsCM-7 Least FunctionalityPR.PS-01Reduces attack surface
Network exposure analysisSC-7 Boundary Protection, CA-7 Continuous MonitoringID.RA-05T1190

Common failure patterns

  • Suppressing on package-level results: a tool falls back to "package imported, function unknown", and the team reads it as not reachable. Only symbol-level results should lower priority.
  • Trusting static analysis in reflective frameworks: dependency injection, serialisation and plugin frameworks call code that no static call graph shows, producing false negatives exactly where deserialisation flaws live.
  • Short runtime windows: a week of observation declares a vulnerable parser "never executed", but it runs only in the quarterly reporting job.
  • Ignoring indirect input: a service with no inbound network path processes files or queue messages that attackers influence, so the "not network reachable" label is wrong.
  • Stale VEX: not_affected statements are copied forward from release to release without re-analysis, until a refactor creates the call path.
  • Using reachability to override exploitation evidence: a KEV-listed flaw in an internet-facing product is deferred because one code path looked unreachable. See the Zero-Day and Exploited Vulnerabilities Guide.

Practitioner checklist

  • Generate SBOMs from builds and images and map every SCA finding to a product version and owner.
  • Run function-level reachability in report-only mode before any suppression.
  • Tag every result by evidence level and allow deprioritisation only for symbol-level not reachable findings.
  • Treat reflection, native code, plugins and deserialisation of untrusted input as reachable unless runtime data proves otherwise.
  • Set a minimum runtime observation window with representative production traffic.
  • Attach exposure class from network reachability analysis and re-tier on every network change.
  • Never let reachability lower a KEV entry on an internet-facing service.
  • Publish a VEX statement with justification and evidence for every suppression, and re-evaluate on every release.
  • Track static and runtime disagreement and VEX reversals, and fix the rule behind each false negative.

Standards and references

Related NHI Mgmt Group resources: SBOM Guide · Vulnerability Prioritisation Guide · Risk-Based Vulnerability Management Guide · Application Security Posture Management (ASPM) Guide · Vulnerability Management Guide

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Written and reviewed by Lalit Choda, NHI Mgmt Group. Last updated 24 October 2026.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.