Semantic reachability
Learning objective
After this chapter you will be able to explain what SemanticReachability adds on top of FrameworkReachability, why it needs more inputs than a single source scan can provide, and how to assemble those inputs through the --bom-dir lifecycle workflow. You will also know how to drive the explanation modes, including LLMPrompts, and what resources to plan for.
What the analyzer adds
SemanticReachability extends FrameworkReachability with three reachability tiers that need context a single source tree cannot supply. The first is endpoint reachability. The analyzer maps OpenAPI x-atom-usages usage targets onto a component's own occurrence locations, so a CVE is endpoint-reachable only when a traced flow reaches a package that is used by a route which is itself exposed. This is the tier that turns "called somewhere" into "callable from the network", which is the escalation that matters most for perimeter triage.
The second tier is service reachability. The analyzer associates SERVICE_TAGS positionally with the nearest purl in a node tag string, so a package that talks to an allow-listed service can be identified and, depending on policy, deprioritized. This is the one tier that often becomes a reason to reduce priority rather than raise it, because a service-bound package on a trusted channel is less exploitable than an unbounded one.
The third tier is post-build and binary reachability. The analyzer matches framework and cryptographic-asset components across lifecycle SBOMs, so a component that is present in the container image but absent from source, or a cryptographic asset that only appears at build time, can be triaged differently from a pure source finding. This is the tier that makes dep-scan's lifecycle story coherent: source reachability and runtime reachability are not the same thing, and the semantic analyzer can tell them apart.
The formulation problem
These three tiers need inputs that dep-scan cannot generate automatically from a single scan, which is sometimes called the formulation problem. Endpoint reachability needs an OpenAPI document with x-atom-usages targets. Post-build reachability needs BOMs for the build output and the container image, not just the source. dep-scan cannot build arbitrary projects or container images for you, and it cannot guess your OpenAPI surface, so the semantic analyzer is only as rich as the inputs you gather. The practical consequence is the --bom-dir workflow, which is the recommended way to run SemanticReachability.
The --bom-dir lifecycle workflow
Instead of scanning a source directory, you generate the lifecycle BOMs yourself, collect them in one directory, and point dep-scan at the directory. dep-scan then selects LifecycleAnalyzer automatically (because auto uses LifecycleAnalyzer when --bom-dir points at multiple xBOMs) and feeds the semantic analyzer the full set.
# Recommended: analyze a directory of lifecycle BOMs
depscan --bom-dir <bom directory> --reports-dir <reports directory> \
--reachability-analyzer SemanticReachability --explain
For a single container image, set the DEPSCAN_SOURCE_IMAGE environment variable so dep-scan can generate the container BOM automatically during the analysis.
export DEPSCAN_SOURCE_IMAGE=<your-image>:<tag>
depscan --src <source directory> --reports-dir <reports directory> -t <language> \
--reachability-analyzer SemanticReachability --explain
For multiple images, such as a docker-compose setup, build the images and generate a BOM for each, then collect them. The steps are: run docker compose build; generate a .cdx.json BOM per image with cdxgen -t docker -o reports/<name>.cdx.json <image>; place every BOM in a single directory; run dep-scan with --bom-dir <that directory>. Ensure the BOM files all correspond to the same version of the source code, because mixing versions produces duplicate-looking component names and unreliable reachability. Use DEPSCAN_BUILD_DIR to point at a build directory when the build output is not under the source root; blint's SBOM command runs against that directory during lifecycle analysis.
Resources and runtime
Semantic analysis, including computing the atom slices that feed all three tiers, is resource-intensive. For larger codebases, allocate over 64GB of memory and expect runtimes of 15 minutes or more. The cost is dominated by slicing, which happens once during BOM generation; the semantic analyzer itself consumes the resulting slices plus the extra BOMs and OpenAPI inputs. Sample semantic analysis reports, including the BOM and slices dataset for real projects, are available in the AppThreat Hugging Face repository, which is useful for sizing your own runs.
Explanation modes
When SemanticReachability is enabled and --explain is passed, the --explanation-mode argument selects what the report emphasizes. The four modes serve different audiences.
Endpointslists all HTTP endpoints, their methods, and code hotspots detected through static analysis. Use it when the endpoint picture is what you need to validate.EndpointsAndReachablesincludes endpoints plus a selection of up to twenty reachable data flows with detailed explanations. This is the default and the starting point for most triage.NonReachableshighlights up to twenty non-reachable data flows, which is useful for showing where validation and sanitization are properly implemented, often for assurance reporting.LLMPromptsgenerates prompt text for use with an LLM when you want code-fix suggestions, which lets you hand the reachability evidence to a downstream model.
depscan --bom-dir <bom directory> --reports-dir <reports directory> \
--reachability-analyzer SemanticReachability --explain --explanation-mode LLMPrompts
Use the MAX_REACHABLE_EXPLANATIONS environment variable to customize the number of data-flow explanations beyond the default of twenty. Use SCAN_DEBUG_MODE=debug when you need the full log surface to diagnose a semantic run that produced no endpoint or service insights.
A reference walkthrough
The Java semantic analysis lesson is the canonical end-to-end walkthrough for this analyzer. It builds dependency-track, pulls its container image, exports DEPSCAN_SOURCE_IMAGE, and runs dep-scan with --reachability-analyzer SemanticReachability --explain, then discusses the difference between the local CdxgenGenerator engine and the default CdxgenImageBasedGenerator. Read it alongside this chapter when you want a full project rather than a per-flag reference. It also demonstrates the leverage of unsetting DEPSCAN_SOURCE_IMAGE to analyze source only, without the container layer, which is a useful way to isolate where a finding originates.
Troubleshooting
If container SBOMs were not generated, the cause is usually the image variable. For simple container scans, pass the image name via --src. For lifecycle or semantic reachability analysis, pass the source directory via --src and the container image name via DEPSCAN_SOURCE_IMAGE so the container BOM is generated as part of the run. If post-build binary SBOMs were not generated, build the application first, because dep-scan cannot build arbitrary projects for you, and point DEPSCAN_BUILD_DIR at the build output. If the semantic run is slow or memory-hungry on a large project, that is expected; size the machine to the figures above, and consider running slicing once and reusing the persisted slices across scans.
Summary
SemanticReachability is the compliance-grade analyzer that adds endpoint, service, and post-build reachability on top of the framework-forward default, at the cost of gathering the lifecycle BOMs and OpenAPI inputs those tiers need. The --bom-dir workflow is the recommended way to supply those inputs, DEPSCAN_SOURCE_IMAGE covers the single-image case, and the explanation modes (including LLMPrompts) let you tailor the report to your audience. Plan for the resource cost, which is dominated by slicing, and read the prioritized set from the VDR or the CSAF status from the VEX. The neighboring chapters to return to are Framework reachability for the lighter analyzer, the VDR guide for the analyst artifact, and the CSAF VEX guide for the compliance artifact.