Vulnerability prioritization
On this page
In plain terms
Run every security control on a real system and you won't get a tidy report, you'll get thousands of findings, a large share labeled "high" or "critical." Vulnerability prioritization is the discipline of turning that flood into a short, ordered list of what deserves attention first. It's the hardest and most valuable problem in application security, because effort is finite and most findings will never be exploited.
Why it matters
When everything is critical, nothing is. A backlog of 2,000 "criticals" isn't a security program, it's a way to guarantee the actually dangerous ones get lost in the noise. Teams either burn out chasing low-value findings or, more often, tune the whole thing out. Good prioritization is what makes security actionable.
The inputs
Severity alone (CVSS) is not priority. A useful ranking folds in context the scanner can't see, and these inputs line up neatly with the CVSS dimensions (base, environmental, threat):
| The input | What it measures |
|---|---|
| Severity | How bad the issue is in the abstract. (the CVSS base score) |
| Exposure | Is the affected component internet-facing, authenticated-only, internal, or fully restricted? A flaw nobody can reach is a very different risk from one on your login page. (environmental) |
| Criticality | The business impact if the component fails, from an outage or data loss down to limited impact that is easily worked around. (environmental) |
| Exploitability | Is it being exploited? (threat. See EPSS & KEV) |
Multiply those together and the list reorders dramatically: a "medium" on a public, business-critical service jumps above a "critical" on an internal batch job.
What moves a finding along the severity axis
The grid above has two axes and you supply one of them. Exposure and criticality are declared once per component, in your descriptor, and they do not change from one finding to the next. That is the whole point of declaring them: they are the half of the answer no scanner can work out.
The other axis is how bad the flaw is, and that is not simply what the scanner called it. Signals can move a finding along it before it reaches the grid, and each takes what the one above it produced.
None of them rewrites what the scanner reported. The report shows the scanner's own rating beside the band it was ranked at, and names the signal that moved it, so the distance between the two is always visible.
Whatever severity comes out of those four is what enters the grid above, on the column axis. Your component's exposure and criticality decide the row. Priority is where they meet.
Here is one real finding making that trip, from a scan of our public sandbox:
Three things are worth reading off that. The scanner's own rating is still medium and the report
still shows it. The band moved because something true about the world met something true about the
component. And neither half would have been enough alone: the same critical on an internal,
supporting component lands at P2.
That finding is also in a package its publisher has deprecated, which on its own would have taken it
to high. Being exploited takes it to critical, so that is the one that decides and the one the
report names. Which is the first of the two collision rules, on a real finding.
Secret scanning ignores the row
Secret scanning is the exception, and it is worth knowing because it is the only place Draugr overrides something you declared. A leaked credential is ranked as though the component were the most exposed and most critical one you have, whatever you classified it as. The report says so on the finding: a leaked credential is high priority wherever it is found.
The reason is that the other four inputs assume a flaw's reach is bounded by the component it is in,
and a credential is not bounded by anything: it is valid wherever it is valid, in a cloud account,
a registry, an artifact store. Git history is also read by more people than most services are reachable by, so
internal can understate who can get hold of it.
Without that override the report contradicts itself. The floor says a credential is serious, exposure and criticality damp it back to P3, and the same six lines tell you nothing is urgent and that you cannot ship.
When two of them land on the same finding
Two that both raise it: the higher one wins. Being exploited and being a bad dependency answer different questions, and neither is automatically the stronger claim, so the larger move decides and the report names which signal made it.
One that raises against one that lowers: it depends what the raise is claiming. "Nothing can reach it" is a statement that something is absent, and absences are the easiest thing to be wrong about. A call path can be created by reflection, by dynamic dispatch, or by somebody writing one line tomorrow. So a signal saying this is dangerous right now beats it, and a signal saying something else does not.
| Signal | Against "nothing can reach it" | Why |
|---|---|---|
| On the KEV catalog | wins | somebody is exploiting it, and a missed path is the expensive mistake |
| High EPSS | wins | the same question, answered by prediction rather than by observation |
| Malicious package | wins | a hostile dependency is a problem whichever of its functions you call |
| Deprecated package | yields | it says who maintains the package, not whether this flaw can fire in your code |
That last row is worth reading twice, because it is the one people get wrong. A vulnerability nothing can reach, in a package whose publisher has walked away, still ranks down. The deprecation is a good argument for replacing that dependency and it stays visible in the report. It is not evidence that this particular flaw is live.
Where it fits
Prioritization sits after your controls run and before your team acts, it's the layer that consumes findings from SCA, SAST, image scanning, and the rest, and produces the ranked worklist. It's also where gating decisions live: fail a build only on what matters, not on every warning.
Common pitfalls
| The pitfall | Why it bites |
|---|---|
| Sorting by severity and calling it done | That's the default that creates the backlog problem in the first place. |
| Context that lives only in someone's head | Exposure and criticality have to be captured (ideally as data next to the code) to be usable automatically. |
| Gating too broadly | Blocking every finding trains people to bypass the gate; block on priority, surface the rest. |
draugr classify wizard help), and Draugr ranks every finding into priority bands P1–P4, --min-priority to focus the list, and a gate that blocks CI on P1 by default rather than on raw severity. Severity is just one input; context does the rest. See how Draugr ranks findings in the docs.
Keep learning
- CVE & CVSS. Why severity is only the starting point
- EPSS & KEV, adding exploitability
- Dependency health, malicious and deprecated packages
- Shift-left & DevSecOps. Where gating happens