Skip to main content

Framework reachability, the default analyzer

Learning objective

After this chapter you will be able to explain exactly what FrameworkReachability computes, what it does not compute, and when it is the right analyzer for a scan. You will also know how to recognize the situations where its limits become visible and you should reach for SemanticReachability instead.

What the analyzer is

FrameworkReachability is the Framework-Forward Reachability (FFR) algorithm and the default value of --reachability-analyzer. It computes reachability by identifying sources and entry points that originate from framework-provided inputs or routes and reach a library sink. Its inputs are two things the BOM and the slicer give it: direct_purls, built from CycloneDX component.evidence.occurrences (where a dependency appears in code), and reached_purls, built from the purls on each slice flow object. A purl that appears in a reachable flow is reached. That is the whole algorithm, and its parsimony is the source of both its speed and its limits. The model behind these maps is developed in the reachability model concept chapter.

The analyzer is slicer-agnostic by design. atom feeds it for Java, JavaScript, Python, and PHP; rusi, golem, and dosai feed it through their converters for Rust, Go, and .NET. From the engine's point of view, every slicer produces the same purl-keyed slice shape, so FrameworkReachability does not know or care which language produced a flow. This is why the default workflow is identical across all seven ecosystems: scan with --profile research, let the slices land, and read the Reachable insights.

What it computes and what it does not

What FFR computes is the answer to the question analysts ask most often: is the vulnerable package actually called? A finding whose purl is in reached_purls is Reachable, and when it also has known exploits it is tagged "Reachable and Exploitable" and fails safe into the reached set to reduce false negatives, as described in How dep-scan prioritizes. That single signal drives the depscan:prioritized property, the Reachable insight, and the corresponding next-steps text.

What FFR does not compute is everything that requires context beyond a single source tree and its slices. It does not attribute endpoints, because it does not consume OpenAPI x-atom-usages usage targets. It does not attribute services, because it does not consume the service tags the semantic analyzer associates with purls. And it does not reason across lifecycle phases, because it has a single BOM rather than a source-plus-build-plus-container picture. A package that is reachable in the container image but absent from source, or a CVE that is only exploitable through an exposed endpoint, will not get the richer escalation that SemanticReachability provides.

When FFR is enough

FFR is the right choice for the majority of triage work, and it is the analyzer every language chapter uses for its worked examples. If your goal is to shrink a long list of version-matched CVEs into the subset where the vulnerable code is actually on an executed path, FFR delivers that with low setup cost and predictable runtimes. It is also the right choice when you do not have, or do not want to pay for, the extra inputs the semantic tiers require: a single source scan with --profile research is enough to populate the maps.

The contrast cases in the language chapters illustrate the boundary cleanly. In Rust, time@0.1.45 is marked Reachable because time::now() is called, which is a pure framework-forward verdict. In .NET, Newtonsoft.Json@13.0.3 is marked Reachable because JsonConvert.DeserializeObject is called on a controlled input, again a framework-forward verdict that needs no endpoint or lifecycle context. These are the cases FFR exists to solve.

When to move beyond FFR

You should reach for SemanticReachability when the question changes from "is it called" to "is it exposed" or "is it deployed". Endpoint-reachable findings, where a traced flow reaches a package used by a route that is itself exposed, need the OpenAPI usage targets the semantic analyzer consumes. Service-reachable findings, where a package talks to an allow-listed service and can therefore be deprioritized, need the service tags. Post-build findings, where a component present in the container image is absent from source, need the lifecycle BOMs that only a --bom-dir workflow provides. The Semantic reachability chapter develops all three tiers and the workflow they require.

There is also a resource angle. FFR is fast because it consumes slices that are already computed. The heavy work, generating the atom intermediate representation and slicing it, happens once during the --profile research BOM generation step regardless of which analyzer you later select. Switching from FFR to SemanticReachability does not re-run the slicer; it consumes the same slices plus additional BOMs and OpenAPI inputs. So the cost of moving to the semantic analyzer is the cost of gathering those inputs, not the cost of re-slicing.

Troubleshooting

Most FFR problems are actually BOM or slicer problems, which is why the SBOM and evidence chapter is the first place to look. If no finding is Reachable, check the *-reachables.slices.json file: an empty or very small file means the slicer found no flows to trace, and a file whose flows lack purls values means the BOM lacked the occurrence and purl information needed for attribution. For large projects, the slicer itself may have crashed from memory exhaustion with messages suppressed unless SCAN_DEBUG_MODE=debug is set; for Python applications such as DejaCode, plan for around 40GB of RAM, and for large Java projects, 32GB is a practical minimum.

If slices are present but you expected a specific package to be Reachable and it is not, confirm the BOM carries a versioned purl for that package. The reconcilers in every converter map by name onto versioned purls, so a versionless purl in the BOM defeats the match even when the flow is correct. This is the single most common cause of a "called but not Reachable" mystery.

Summary

FrameworkReachability is the fast, slicer-agnostic default that answers whether a vulnerable package is actually called, using direct_purls from BOM occurrences and reached_purls from slice flows. It is enough for most triage, and it is the analyzer behind every reachable-versus-present example in the language guides. Move to SemanticReachability only when you need endpoint, service, or lifecycle reachability, and treat most FFR problems as BOM or slicer problems first. The next chapter, Semantic reachability, covers the heavier analyzer and the --bom-dir workflow it depends on.