Application Security

Function Reachability

Determines whether the specific vulnerable function inside a dependency is actually called by an application's code, distinguishing installed risk from exercised risk.

What is function reachability?

Function reachability determines whether the specific vulnerable function inside a dependency is actually called by your code, directly or through the call graph. A package can be installed, imported and loaded while the vulnerable function it contains is never invoked — in which case the finding describes a risk you do not carry.

Key takeaways

  • Present is not loaded, loaded is not callable, callable is not exploitable. Each layer removes findings, and most software composition analysis stops at the first one.
  • It is a static analysis of the call graph. It reasons over your code and its dependency tree without executing anything or instrumenting a running process.
  • It answers a narrower question than taint analysis. Reachability confirms the function is called. Taint analysis confirms attacker-controlled data reaches its vulnerable parameter.
  • Log4Shell is the canonical illustration. A Java application shipped Log4j, but if it was not using that logging engine the vulnerable code was never callable — loaded, and zero exposure.
  • Framework and dynamic patterns are the hard cases. Reflection, dependency injection and dynamic dispatch break naive call graphs, and how a tool handles them is the real quality difference.

Why it matters

Software composition analysis produces the largest finding counts in application security, and the great majority of those findings are not exploitable. Developers learn this quickly, and the rational response to a queue that is mostly noise is to stop reading it — which is how genuinely exploitable dependency vulnerabilities end up sitting alongside hundreds that were never callable.

Filtering by reachability is what makes the remainder tractable. How much is removed varies widely with the language ecosystem, the depth of the dependency tree and the scanner in use, so it is a proportion to measure against a specific backlog rather than to quote from someone else's. The direction is consistent even where the magnitude is not: the queue that reaches a developer stops being mostly noise, which is the precondition for it being read at all.

The layers, from cheapest to most precise

  • Presence. The vulnerable package version appears in the manifest or lockfile. This is what a plain SCA finding means, and it is where most tooling stops.
  • Load. The package is actually imported and present in the running application. Narrows the set, and still says nothing about the vulnerable function.
  • Function reachability. There is a path through the call graph from your code to the vulnerable function. This is the layer that removes the most findings.
  • Data reachability. Attacker-controlled input can reach the vulnerable parameter of that function. The strongest static signal, and the one that most closely resembles proof.
  • Demonstrated exploitability. A crafted input produces the effect the advisory describes in a replica of the affected configuration. Beyond the reach of static analysis, which is why reachability is an elimination mechanism rather than proof.

A worked example: Log4Shell, loaded and uncallable

CVE-2021-44228 in Apache Log4j 2 is the case the technique is usually explained with, because the gap between present and callable was unusually wide and unusually well documented.

The vulnerable artifact is log4j-core, and in a large Java application it arrives by several routes at once: declared directly in one module, pulled in transitively by a framework in another, and shadowed inside a fat jar built by a different team. A manifest scan reports it every time. What determines exposure is whether the application's logging facade is bound to Log4j 2 at all. A service that binds its facade to a different backend ships the vulnerable class on the classpath and never calls it.

Function reachability asks the narrower question: is there a path through the call graph from application code to the message-lookup routine that performs the JNDI resolution. Where the facade binds elsewhere, no such path exists, the jar is loaded and the exposure is zero. Where it does bind to Log4j 2, the path exists and the finding is real — at which point the next question is data reachability, whether attacker-controlled text can reach a logged parameter, which for a request header it almost always can.

Function reachability versus runtime observation

Runtime approaches instrument the running application and observe what is actually executed. That is a genuinely strong signal for code paths that get exercised, and it has two structural costs: the agent has to live in every environment and language runtime that matters, and a path never exercised by test or production traffic produces no finding at all. Absence of observation is not absence of reachability.

Static function reachability has the opposite profile. It runs against the codebase itself with no execution dependency and no agent deployed into a running process, so it covers paths that traffic has not exercised — but it must reason about dynamic behaviour rather than watch it, which is where framework magic and reflection make the analysis hard.

Where function reachability stops

A reachable function is not automatically an exploitable one. The call may pass only trusted, internally generated values; the vulnerable branch may require a configuration you do not use; an upstream control may sanitise the input. This is why reachability is best understood as an elimination mechanism — extremely good at ruling findings out, weaker as positive proof.

It is also confined to the code lane. Reachability on assets is a different mechanism entirely, based on file paths, listening ports and whether a component is loaded and running, and the two should not be conflated in a single coverage number.

What to look for in an evaluation

  • Handling of dynamic dispatch. Reflection, dependency injection, service loaders and configuration-driven wiring. Ask what the analysis assumes when it cannot resolve a call, and in which direction it errs.
  • Ecosystem parity. Call-graph quality differs sharply between a statically typed compiled language and a dynamic one. A single reachability claim spanning Java, Python, JavaScript and Go rarely means the same thing in each.
  • Transitive depth. Whether the analysis follows the graph through intermediate dependencies or stops at the first-party boundary, which is where a large share of the interesting paths live.
  • The evidence returned. A verdict without the call path is an assertion. A reviewer should be able to read the chain from an entry point to the vulnerable function and disagree with it.
  • Behaviour on ambiguity. An unresolvable path can be reported as reachable, unreachable or unknown. Only the third answer is honest, and only some tools offer it.

FAQ

Does function reachability require running the application? No. It is a static analysis over the codebase and its dependency tree. Runtime approaches answer a related question by observing execution, but they need an agent in every environment and see only the paths traffic exercises.

What about reflection and dependency injection? They are the hard cases and the honest limit of the technique. A call graph that ignores them under-reports reachability; one that assumes the worst over-reports it. How a tool handles dynamic dispatch is the question worth asking in an evaluation.

If a function is unreachable, should the dependency still be upgraded? Usually yes, eventually — on the routine maintenance cadence rather than as a security escalation. Unreachable today is a statement about the current code, and code changes.

Sources

  • Apache Software Foundation, security advisory for CVE-2021-44228 (Log4Shell), 2021.
  • Cybersecurity and Infrastructure Security Agency, Known Exploited Vulnerabilities Catalog, 2021-2026.
  • OWASP, Top 10:2021, A06 Vulnerable and Outdated Components.
  • OWASP CycloneDX, specification version 1.6, 2024.
  • Verizon, 2026 Data Breach Investigations Report.