Notes · Dissecting Real Systems
growing
Four Hundred Findings
A scanner report is not a list of your vulnerabilities. It is a list of your dependencies crossed with a database — and about one in thirty is reachable from code you actually run.
Conducting a risk assessment of each new vulnerability in order to plan the optimal risk response for it is simply not feasible. Organizations do not have the time, resources, expertise, or tools to do so.
— Murugiah Souppaya and Karen Scarfone, Guide to Enterprise Patch Management Planning (NIST SP 800-40 Rev. 4, 2022)
Cite this
Mangalapilly, Y. J. (2026, August). Four Hundred Findings. Saṃhitā Notes. https://yesudeep.com/blog/four-hundred-findings/ @online{mangalapilly2026four,
author = {Yesudeep Jose Mangalapilly},
title = {Four Hundred Findings},
journal = {Sa\d{m}hit\=a Notes},
year = {2026},
month = {August},
url = {https://yesudeep.com/blog/four-hundred-findings/},
urldate = {2026-08-12},
} Yesudeep Jose Mangalapilly. “Four Hundred Findings.” Saṃhitā Notes, 2026. https://yesudeep.com/blog/four-hundred-findings/. TY - ELEC
AU - Mangalapilly, Yesudeep Jose
TI - Four Hundred Findings
T2 - Saṃhitā Notes
PY - 2026
UR - https://yesudeep.com/blog/four-hundred-findings/
Y2 - 2026-08-12
ER - The vocabulary, and the one question that shrinks the list. The vulnerability-management article in the web security series examines the report rather than the mechanism. By the end you'll know what each identifier and score actually claims, where to look each of them up, why "patch everything critical" is a policy the standards bodies have themselves abandoned, what reachability analysis buys and what it provably misses, and how to write down a decision so the next person does not redo it.
Start with the disagreement.
Nasif Imtiaz, Seaver Thorn and Laurie Williams at NC State ran nine dependency scanners over the same projects and compared what they reported (ESEM 2021). The result is the most useful single fact in this article:
The count of reported vulnerable dependencies ranges from 17 to 332 for Maven and from 32 to 239 for npm projects across the studied tools.
Same code, same day, twentyfold spread. Before anything else, that tells you what a scan output is: not an observation about your software, but the output of one tool's database joined against one tool's idea of your dependency tree.
"How many vulnerabilities do we have?" is not a question about your software. It is a question about your scanner.
Why the scores do not produce a work list
Put the definitions side by side and the gap is obvious. CVSS knows how bad the weakness is in the abstract. EPSS knows whether the internet is exploiting it. KEV knows whether anyone ever has. None of them knows anything about your codebase.
That is not a criticism — none of them claims to. But it means any policy of the form "patch everything above X" is sorting by a property that is constant across every organization using that dependency. Your list and a stranger's list are the same list.
Imagine a safety notice listing every part ever recalled in any car. It is accurate, it is useful, and it tells you nothing about whether your car has that part — let alone whether the part is connected to anything. Reading the whole list carefully is not the same as inspecting your car.
When the list is wrong in the other direction
Everything above is about a list too long to act on. The list can also be wrong the other way, and the canonical case is worth stating precisely, because it is usually told as a story about negligence and it is really a story about instrumentation.
The Apache Software Foundation published a patch for CVE-2017-5638 on 7 March
- Eight days later, on 15 March, Equifax ran scans intended to find
systems affected by it. The GAO's account records that the scans did not identify the vulnerable system. The dispute portal running the affected Struts version stayed unpatched until 29 July. Between May and July, attackers took data on about 147 million people, and the settlement with the FTC, CFPB and 50 states and territories was at least $575 million.
Nobody deprioritized this finding. The finding never appeared. A scan ran, returned nothing for that CVE, and the absence was read as an answer.
That is the failure mode this article's whole method has to survive. Every technique here reduces a list, and a reduction is only as good as the inventory it started from. A scanner reports what it reached; it says nothing about what it did not reach, and those two are indistinguishable in the output. Before ranking findings, the question that decides everything is whether the thing you are worried about is in scope of the tool at all.
Note
The reduction techniques below and this failure are the same subject seen twice. Reachability shrinks a list by proving a component cannot be used; coverage gaps shrink a list by never mentioning the component. The first is evidence, the second is silence, and only one of them is trustworthy.
The question that shrinks the list
Here is the ladder between "a scanner said so" and "an attacker could use this." It is not folklore — CISA formalized it as the justification vocabulary for VEX documents.
The third rung is where most of the reduction happens, and CISA's own example is perfect:
Applications that only used OpenSSL for secure random function were not vulnerable to the Heartbleed protocol vulnerability which was never called from secure random.
The dependency was present. The vulnerable version was present. The vulnerable function was never called. Three different facts, and only the last one decides anything.
What the measurements say
The best available number is peer-reviewed rather than vendor-sourced. Amir M. Mir, Mehdi Keshani and Sebastian Proksch at TU Delft built whole-program call graphs over three million Maven packages at method-level granularity, and reported (SANER 2023):
(1) about 1/3 of packages in our dataset are identified as vulnerable if and only if all the transitive dependencies are considered. (2) less than 1% of packages have a reachable call path to vulnerable code in their dependencies, which is far lower than that of a naive dependency-based analysis.
About a third look vulnerable by metadata. Under one percent are reachable. Almost everything in a dependency scan is a fact about a package you depend on, not about your program.
The mechanism behind that ratio was measured separately. Joseph Hejderup and colleagues, building call graphs for Rust's crates.io, found that packages "call only 40% of their resolved dependencies" (Präzi, EMSE 2022) — you install a library and use part of it, and the parts you never call still carry their advisories.
And a rung further up, Ivan Pashchenko and colleagues found "about 20% of the dependencies affected by a known vulnerability are not deployed, and therefore, they do not represent a danger" — test-only and build-only dependencies that never ship.
Note
Vendors publish reachability numbers too — Endor Labs, Datadog, Snyk and others build products on exactly this analysis. Those figures may well be right, but they are measured with the vendor's own tool on a self-selected customer population, and the tool is the instrument. Prefer the papers when you are deciding policy; use the vendors when you are choosing tooling.
What reachability provably misses
An article that ended there would be selling something. Static reachability analysis is unsound, deliberately, and the field has a word for it.
In Defense of Soundiness: A Manifesto — Ben Livshits and colleagues, CACM 2015 — names the situation plainly:
we are not aware of a single realistic whole-program analysis tool… that does not purposely make unsound choices.
The constructs that get approximated are exactly the ones a real program uses:
it is conventional for an otherwise sound static analysis to treat highly-dynamic language constructs, such as Java reflection or eval in JavaScript, under-approximately.
Reflection in particular "can render much of the codebase invisible for analysis." And the authors note these features "are nearly ubiquitous in practice. Assuming the features away excludes the majority of input programs."
Go's govulncheck is the best case available — statically typed language, first-party tool, curated database — and its marketing page says it "only surfaces vulnerabilities that actually affect you." Its own reference documentation is more careful:
Calls to functions made using package reflect are not visible to static analysis. Vulnerable code reachable only through those calls will not be reported in source scan mode. Similarly, use of the unsafe package may result in false negatives.
Warning
Reachability shrinks the list; it does not prove safety. CISA attaches the same caveat to its own execute-path justification: "a single path of execution should not be assumed since the attacker may be able to divert the path of execution." Treat an unreachable finding as deprioritized with a recorded reason, not as closed forever.
Writing the decision down
Deciding a finding does not apply is the expensive part. Doing it twice because nobody recorded it is the waste.
VEX — Vulnerability Exploitability eXchange — is the machine-readable form of that decision, with four statuses: NOT AFFECTED ("No remediation is required regarding this vulnerability"), AFFECTED, FIXED, and UNDER INVESTIGATION. The reachability ladder's justifications are the permitted reasons for the first.
Important
"VEX" names a concept with several incompatible implementations, and the identifiers differ: CISA's prose says NOT AFFECTED, OASIS CSAF uses known_not_affected, OpenVEX uses not_affected. CISA is explicit that its own document "does not represent official CISA policy" and that its drafting "did not follow a formal standards development process." CSAF 2.0 is the only de jure standard among them. Pick one and be consistent; do not assume documents interoperate.
For the data underneath, OSV is worth knowing about specifically because it fixes a matching problem. Its schema, by Oliver Chang and Russ Cox, exists because "matching a vulnerability such as a CVE to a package name and set of versions in a package manager is difficult to do in an automated way using existing mechanisms such as CPEs." OSV keys on an ecosystem-plus-name pair and precise version ranges — npm/zlib and PyPI/zlib "denote different libraries," which a CPE string cannot reliably express.
The policy caught up
The most striking evidence that flat severity policies were wrong is that the people who mandated them stopped.
CISA's Binding Operational Directive 22-01 gave US federal agencies two weeks to remediate KEV entries. It is the origin of most "patch everything in KEV within 14 days" policies in the industry. As of June 2026 it is revoked, superseded by BOD 26-04, whose title says what changed: Prioritizing Security Updates Based on Risk.
The replacement is a decision table over four variables — whether the system is publicly exposed, whether the vulnerability is in KEV, whether exploitation is automatable, and the technical impact — producing outcomes from "three days, with forensic triage" to "fix on system upgrade." KEV membership is now one input of four rather than a deadline.
That structure comes from SSVC, the Stakeholder-Specific Vulnerability Categorization designed at Carnegie Mellon's SEI with CISA. It replaces a score with a decision tree whose four outcomes are actions rather than numbers:
- Track — "does not require action at this time," remediate within standard update timelines.
- Track\* — the same, but "contains specific characteristics that may require closer monitoring."
- Attend — "requires attention from the organization's internal, supervisory-level individuals," sooner than standard timelines.
- Act — attention "from the organization's internal, supervisory-level and leadership-level individuals," as soon as possible.
Note that even Exploitation = Active does not by itself produce Act; several active-exploitation rows land on Track because the other branches matter. That is the whole design: a decision tree cannot be collapsed into a threshold.
A workflow that fits in a sprint
NIST's patch management guide is unusually candid about the constraint, which is why it opens this article. Its guidance is to simplify decision making rather than assess everything, and it offers four risk responses — accept, mitigate, transfer, avoid — with a default worth quoting: "By default, an organization accepts the risk posed by using its software."
Putting the pieces together, in the order that removes the most work earliest:
- Deduplicate against deployment. Test-only and build-only dependencies do not ship. The deployment filter requires no reachability analysis and removes roughly a fifth.
- Filter by reachability where a tool exists for your language —
govulncheckfor Go is the mature case. The reachability filter is the thirty-to-one step. - Sort what remains by exploitation, not severity. KEV membership first, then EPSS. Both are about the world; neither is about your code, which is why they come after step 2 rather than before it.
- Decide with a tree, not a threshold. SSVC's four outcomes, or BOD 26-04's table if you want one someone else has already defended.
- Record the decision as VEX, with the justification from the ladder. The next scan will report the same finding, and the next person should not have to re-derive the answer.
- Re-examine on change, not on schedule. A finding that is unreachable at one revision becomes reachable the moment someone calls the function — which is a code review concern, not a scanning concern.
The dashboard was red and the number was 412. Leadership asked for zero by end of quarter, which was reasonable-sounding and became the whole plan.
Two engineers spent six weeks upgrading dependencies. Most upgrades were uneventful; four required code changes; one introduced a regression that took a week to find. The number reached numbers like 380, then 340, then 291. Nobody had checked whether any of them were reachable, because checking was not on the plan and the plan was the number.
The finding that mattered was in the list the whole time — a template library whose vulnerable function the application called directly on a request path. It was CVSS 6.1, which put it below the cutoff the team had adopted to make the work tractable, and it was fixed eleven weeks later by someone reading the changelog for an unrelated reason.
Lessons
- A scan count is a property of the scanner. Seventeen to 332 findings on the same project, across tools.
- CVE is a name, CWE is a class, CVSS is severity, EPSS is probability, KEV is evidence. Five vocabularies, five questions.
- CVSS says not to use base scores alone. The specification is explicit, and the scoring error is wide enough to cross decision boundaries.
- KEV is under one percent of known CVEs. That ratio is the argument against treating findings alike.
- SEV is incident severity and belongs to a different vocabulary — one with no standard definitions.
- None of the scores knows your codebase. Reachability is the only question in this article that does.
- About a third look vulnerable; under one percent are reachable. Peer-reviewed, three million packages.
- Reachability is unsound on purpose. Reflection and dynamic dispatch are invisible to it, and the good tools document this themselves.
- Record decisions as VEX, and treat an unreachable finding as deprioritized with a reason rather than closed.
- The mandate that started "patch KEV in 14 days" was revoked in 2026 and replaced with a decision table. The policy caught up with the argument.
Practice
References
- Nasif Imtiaz, Seaver Thorn, Laurie Williams. “A Comparative Study of Vulnerability Reporting by Software Composition Analysis Tools.” ESEM, 2021. — the 17-to-332 spread across nine scanners on the same projects
- Amir M. Mir, Mehdi Keshani, Sebastian Proksch. “On the Effect of Transitivity and Granularity on Vulnerability Propagation in the Maven Ecosystem.” SANER, 2023. — the headline result: about 1/3 vulnerable by metadata, under 1% by reachable call path
- Joseph Hejderup, Moritz Beller, Konstantinos Triantafyllou, Georgios Gousios. “Präzi: From Package-based to Call-based Dependency Networks.” Empirical Software Engineering, 2022. — the mechanism — packages call only 40% of their resolved dependencies
- Ivan Pashchenko, Henrik Plate, Serena Elisa Ponta, Antonino Sabetta, Fabio Massacci. “Vulnerable Open Source Dependencies: Counting Those That Matter.” ESEM, 2018. — about 20% of affected dependencies are never deployed
- Benjamin Livshits, Manu Sridharan, Yannis Smaragdakis and others. “In Defense of Soundiness: A Manifesto.” Communications of the ACM 58(2), 2015. — why every realistic whole-program analysis makes deliberate unsound choices
- FIRST. “Common Vulnerability Scoring System version 4.0.” FIRST. — the specification and user guide, including the warning against using base scores alone
- FIRST. “Exploit Prediction Scoring System.” FIRST. — the 30-day probability model, its calibration, and its disclaimer of scope
- CISA. “Known Exploited Vulnerabilities Catalog.” CISA. — inclusion criteria, and the exploitation-versus-exploitability distinction
- CISA. “BOD 26-04: Prioritizing Security Updates Based on Risk.” CISA Binding Operational Directive, 2026. — the directive that revoked BOD 22-01's flat KEV deadline and replaced it with a decision table
- Jonathan Spring, Allen Householder and others. “Stakeholder-Specific Vulnerability Categorization.” Carnegie Mellon SEI. — the decision tree, its four outcomes, and the critique of CVSS scoring error
- CISA. “Minimum Requirements for Vulnerability Exploitability eXchange (VEX).” CISA, 2023. — the four statuses, the justification vocabulary, and its own disclaimer of standards status
- Oliver Chang, Russ Cox. “Open Source Vulnerability schema.” OSV. — ecosystem-plus-name keying and precise version ranges, and why CPE matching fails
- Murugiah Souppaya, Karen Scarfone. “Guide to Enterprise Patch Management Planning.” NIST SP 800-40 Rev. 4, 2022. — simplify decision making, the four risk responses, and the default of accepting risk
- Go team. “Go vulnerability management.” Go. — the reachability-filtered scanner, and the reflection and unsafe false negatives it documents
How to cite
Mangalapilly, Y. J. (2026, August). Four Hundred Findings. Saṃhitā Notes. https://yesudeep.com/blog/four-hundred-findings/ @online{mangalapilly2026four,
author = {Yesudeep Jose Mangalapilly},
title = {Four Hundred Findings},
journal = {Sa\d{m}hit\=a Notes},
year = {2026},
month = {August},
url = {https://yesudeep.com/blog/four-hundred-findings/},
urldate = {2026-08-12},
} Yesudeep Jose Mangalapilly. “Four Hundred Findings.” Saṃhitā Notes, 2026. https://yesudeep.com/blog/four-hundred-findings/. TY - ELEC
AU - Mangalapilly, Yesudeep Jose
TI - Four Hundred Findings
T2 - Saṃhitā Notes
PY - 2026
UR - https://yesudeep.com/blog/four-hundred-findings/
Y2 - 2026-08-12
ER - Webmentions
Annotations
Thank you — your note is held for review and will appear once approved.
Thank you — your note is published.
Please sign in below to leave a note.
