A dependency scanner reports a critical vulnerability in a library your application includes. Reading the advisory, the flaw is in a parser your code never calls. Why does the scanner report it anyway, and what should that change about how you triage?
Show the full answer Hide the answer
The mechanism
A standard dependency scanner compares the list of packages and versions your build resolves
against a vulnerability database. That is all it does. It reads your lock file, matches
library@2.3.1 against an advisory that says "affects 2.0.0 to 2.4.0", and reports a finding.
It has not looked at your code. It does not know which functions you call, which code paths are reachable from your entry points, or whether the vulnerable parser is ever loaded. Presence in the dependency graph is the entire test, which is why a single transitive dependency pulled in by a build tool can produce a critical finding in a service that never executes a line of it.
This is a deliberate design choice, not a defect. Matching versions is fast, needs no language support, and never misses a vulnerable version that is present. The cost of that completeness is false urgency: on a first run across a real estate, findings in the thousands are normal and something on the order of 5-10% are reachable from the application's own entry points. The rest are real flaws in code your service never loads.
What it changes about triage
Severity in the advisory is a property of the library, not of your system. The questions that order the work are yours:
- Is the vulnerable code reachable from our entry points? Some tooling now performs call-graph reachability analysis and can answer this; where it cannot, a grep for the affected API is a crude but useful first pass.
- Is the input attacker-controlled? A deserialisation flaw reached only by a config file you write yourself is a different risk from one reached by an HTTP request body.
- Is it in a runtime dependency or a build dependency? A vulnerability in a test framework does not ship to production, though it can still compromise the build, which is a real but different threat.
This is also why the inventory has become a procurement requirement rather than a nicety: the 2021 US executive order on cybersecurity pushed software bills of materials into federal supply-chain terms, and the reason is the December 2021 Log4j episode, where the expensive part for most organisations was not patching but discovering where the library was.
The ordering that results usually looks nothing like sorting by CVSS score, and that is the point.
Why this matters more than it sounds
A scanner whose findings are not triaged produces a queue that is ignored, and an ignored queue is worse than no scanner, because the organisation believes it is covered. The common outcome is a team that turns the gate to warning-only within a month and never turns it back.
Two practices keep it honest: gate on the delta — block a build that adds a new critical finding, rather than blocking every build until a historical backlog is clear — and keep an inventory of what you depend on (a software bill of materials) so that when the next widely-exploited flaw appears, the question "are we affected" is a query rather than a project.
When not to build a triage process
For a small, well-understood service with few dependencies, do not build a triage process: update the library. Choose to patch rather than to analyse unless the volume makes patching everything impossible, because deciding whether you need a patch usually costs more than applying it. The threshold is roughly where the estate produces more findings per week than a person can read, and below it a triage process fails in the ordinary way: it becomes a backlog nobody works.