How dep-scan prioritizes findings
Learning objective
After this chapter you will be able to read a dep-scan Top Priority table and explain, in terms of the underlying maps, why a given CVE is prioritized or deprioritized. You will also recognize the four escalation tiers (reachable, endpoint-reachable, service-reachable, and the malicious-and-reachable top-priority incident) and know which CycloneDX property encodes the verdict for downstream tools.
The prioritization property
Every vulnerability dep-scan emits carries a CycloneDX property named depscan:prioritized whose value is true or false. The property is set in get_vuln_properties from analysis_lib/utils.py, and the decision is made by analyze_cve_vuln, which receives the four reachability maps described in the reachability model: reached_purls, direct_purls, reached_services, and endpoint_reached_purls. When the analyzer decides a package requires attention, depscan:prioritized becomes true.
The property matters beyond the console. During deduplication, combine_vdrs in utils.py prefers the prioritized entry when two VDR records collide (via _is_prioritized), so a prioritized finding is never silently dropped by a merge. The VDRResult object exposes the result as prioritized_pkg_vuln_trees, which is exactly what the Top Priority table renders. If you ingest the VDR into an ASPM or vulnerability-management platform, filtering on depscan:prioritized is the fastest way to reproduce the console's focus list.
What makes a CVE require attention
analyze_cve_vuln sets cve_requires_attn for several reasons, and reachability interacts with each of them. The reachability-driven triggers are the ones to internalize.
A package becomes reachable when its purl is in reached_purls, and endpoint-reachable when it is in endpoint_reached_purls. Either can set cve_requires_attn, and the severity of the rating influences whether an endpoint-reachable finding crosses the threshold (endpoint-reachable high or critical findings always require attention). When a package has known exploits and is also reachable, dep-scan fails safe: it promotes the package into reached_purls even when no explicit flow was traced, to reduce false negatives, and tags the insight "Reachable and Exploitable". Bug-bounty and proof-of-concept targets get their own escalations when they are reachable, producing insights such as "Reachable Bounty target" and "Endpoint-Reachable Bounty target".
The four tiers therefore read, from least to most urgent: present (in direct_purls only), reachable (reached_purls), endpoint-reachable (endpoint_reached_purls), and service-reachable (reached_services, which is more often a reason to deprioritize because the package talks to an allow-listed service). The next section explains how these become the prose next-steps.
The ladder below reads from bottom (lowest urgency) to top (highest). The malicious-and-reachable incident sits above the ordinary tiers because it combines a malware verdict with any reachability signal.
┌──────────────────────────────────────────────────────────┐
top │ MALICIOUS + REACHABLE -> top-priority security incident │
└──────────────────────────────────────────────────────────┘
▲ escalates above ordinary critical CVEs
┌──────────────────────────────────────────────────────────┐
│ ENDPOINT-REACHABLE endpoint_reached_purls │
│ reached via a route that is itself exposed │
├──────────────────────────────────────────────────────────┤
│ REACHABLE reached_purls │
│ vulnerable function is on a traced path -> prioritized │
├──────────────────────────────────────────────────────────┤
│ PRESENT (imported) direct_purls only │
│ in the report, depscan:prioritized = false │
bottom └──────────────────────── ──────────────────────────────────┘
SERVICE-REACHABLE (reached_services) sits to the side: it usually
lowers priority, since the package talks to an allow-listed service.
The Top Priority table and next-steps
The console "Top Priority" table lists the packages whose CVEs earned depscan:prioritized. Each row pairs the package purl with its prioritized CVEs, a fix version when one is known, and a "Next Steps" column that is generated per finding by find_next_steps in analysis_lib/output.py. The next-steps text is where the four reachability flags become human guidance, and the logic is worth knowing because it explains the wording you see.
find_next_steps sets is_reachable, is_endpoint_reachable, and possible_reachable_service from the same maps, then composes guidance accordingly. A reachable, exploitable finding prompts you to add workarounds and validations to make the CVEs non-reachable; an endpoint-reachable finding points you to framework validation options; a service-reachable finding suggests you confirm the package uses an allow-listed service. The most severe branch is reserved for malware: when check_malware_cve flags a package and it is also reachable or endpoint-reachable, the next-step string becomes a "top-priority security incident". This is why a malicious-and-reachable package is the highest escalation in dep-scan, above ordinary critical CVEs.
A worked example: the java-sec-code Top Priority table
The sample report for java-sec-code (available in the Quick Start sample report) is a good illustration of prioritization in action. Its Top Priority table includes pkg:maven/org.apache.tomcat.embed/tomcat-embed-core carrying roughly sixteen CVEs with a fix version of 8.5.99. The Next Steps column reads, in part, "With 16 vulnerabilities, identify the challenges involved in updating this package to version '8.5.99'. With potentially exploitable CVEs present, care must be taken to manage the risks." That phrasing comes straight from the len(cve_list) > 5 plus is_exploitable branches of find_next_steps: many CVEs on a single prioritized package, some flagged exploitable, and a known fix version. A reader who understands the prioritization logic can tell, without opening the VDR, that tomcat-embed-core is in the table because it is both heavily burdened with CVEs and tagged exploitable, and that the recommendation is a coordinated upgrade rather than a panic.
Contrast that with a present-but-unreachable dependency elsewhere in the same BOM. Such a package never appears in the Top Priority table at all, because without reached_purls or endpoint_reached_purls it cannot set cve_requires_attn on reachability grounds. It will still appear in the full Vulnerability Disclosure Report, but it carries depscan:prioritized=false and no reachable insight, which is exactly the signal a triager needs to move on.
Reading the prioritized set in the VDR
To inspect the prioritized set programmatically, open the <base>.vdr.json (or depscan-universal.vdr.json for multi-BOM runs) and filter the vulnerabilities array on entries whose properties contain {"name": "depscan:prioritized", "value": "true"}. Each such entry also carries a depscan:insights property joining the plain-text insight labels ("Reachable", "Endpoint-Reachable", "Reachable and Exploitable", and so on), which lets you reconstruct the tier that drove the prioritization. The VDR guide walks through this anatomy in detail.
Summary
Prioritization is the bridge between the reachability maps and the guidance an analyst acts on. The depscan:prioritized property is the machine-readable verdict, the Top Priority table is its console rendering, and the next-steps text encodes the four reachability tiers plus the malicious-and-reachable incident escalation. When you understand that reachable findings earn priority while present-but-unreachable ones do not, the difference between two rows in a dep-scan report stops being arbitrary. The next concept chapter, SBOM and evidence, explains how to make sure the BOM carries the occurrence data this whole pipeline depends on.