← All guides

What to fix first: from findings to changes

Cross-cutting
On this page

In plain terms

A fix list is a scan's findings rewritten as the changes that clear them, one row per change. Each row names the change, such as upgrading a library, editing a line of code, changing a setting or rotating a password, and counts the findings it clears. Each finding already carries a priority band, P1 for the most urgent down to P4, set from its severity and from what the descriptor, the committed file that declares a project's components, says about where the component runs and how much depends on it.

Why it matters

One change usually clears many findings. A library with eight known vulnerabilities takes one upgrade, and an old base image, the operating system layer a container is built on, can carry five hundred findings that moving to one newer image clears. Listed one finding per row, that image fills the first page of the report, and a reader cannot tell whether they are looking at five hundred problems or one.

The changes also belong to different people. A developer edits code, a platform engineer changes a cluster setting, and a team using an image somebody else publishes cannot upgrade a package inside it, only move to a newer image. A single list mixing all of these leaves each reader to work out which rows are theirs, and a row nobody reading it can act on teaches them to stop reading.

A reader follows a list when they can check why each row sits where it does, which takes an order built from values the report records and anyone can compare.

How it works

Findings become changes

Each row groups the findings that one change clears:

Findings fromGrouped byThe change
a dependency scan (SCA)package and installed version, in one componentupgrade to the release that fixes all of them
an image somebody else publishesthe imagemove to a newer image
code analysis (SAST)file and linechange that line
infrastructure coderuleapply one fix across the files it flags
secret scanningruleremove the credential and rotate it

Grouping stops where the change stops being one change. Twelve failed checks against one cluster are twelve settings to change, and a rule that flags ten lines of code is ten edits.

Changes are sorted by who makes them

ListWhat it holds
Bump a dependencyan upgrade to the release that fixes a dependency, in a repository or an image you build, or a move of an image or repository somebody else publishes when any of its findings has a fix published
Change the codea change to the application's code or its server's configuration
Change the infrastructurea change to infrastructure code or a live setting
Rotate a credentiala committed secret to remove and then rotate
Wait for a releasea fix nobody has published yet: a package with no fixed release, or an image or repository none of whose findings has one
Decidea decision rather than a change: a license to accept or replace, an operating system past end of service life

The lists always appear in this order, and the order says nothing about urgency. Each list is ordered on its own, so the first row of Change the code is the most urgent change to code even when an image in Bump a dependency clears hundreds of findings. Every change is in exactly one list.

Each list is ordered key by key

To order two rows, compare them on these keys from the top, and the first key where they differ decides.

KeyWhich row comes first
1Prioritythe one clearing the more urgent band, P1 to P4. A change clearing one P1 comes before one clearing forty P4s
2Exploitationthe one clearing a flaw on CISA's Known Exploited Vulnerabilities (KEV) catalog, then the one clearing a flaw whose EPSS score, an estimate of how likely it is to be exploited in the next 30 days, is above the descriptor's threshold. Both apply where config.exploitability turns them on
3Severitythe one clearing the higher severity, after KEV or EPSS raised it or reachability lowered it
4Countthe one clearing more findings
5Upgrade policythe step within the component's upgrade policy. A component that takes only minor upgrades gets two rows for a fix that needs a major one, the minor step and the step past it marked major · beyond policy minor. The step within the policy comes first because the other presumes it, and both rank together, by the more urgent of the two, so they sit side by side

Two rows that match on every key keep the order of the findings list, which ends on component, repository, location and rule, so the same scan produces the same list every time.

Count is near the end because it measures how a scanner counts more than how much a change matters. A scanner that reports every advisory against every package counts an old image in the hundreds, and ranked on volume that image would sit above a single flaw attackers are using today.

Where each key is recorded

The band, the severity and whatever changed the severity are recorded on each finding, in the report and the SARIF file a scan writes, and the upgrade policy is fixes.upgrade in the descriptor. A KEV escalation names the feed and the date its copy was fetched:

"escalation": {
  "from": "medium",
  "to": "critical",
  "signal": "kev",
  "detail": "on KEV",
  "asOf": "2026-10-25",
  "alsoMatched": [
    { "signal": "epss", "detail": "EPSS 0.85", "asOf": "2026-10-25" }
  ]
}

To check why one row sits above another, compare those values on the findings each row clears. Every key is on a finding, in the descriptor or a count of findings, so the order can be rebuilt by hand.

What the list cannot know

Whether a service faces the internet, and what stops when it fails. Priority folds in each component's declared exposure and criticality, because no scanner can see either. A component declared internal that is in fact public ranks too low. The declaration lives in the descriptor, a file the team reviews like any other change, which is where a wrong one gets noticed and fixed.

Whether an image or a cluster is yours. builtBy: upstream on an image marks one somebody else publishes, which turns its findings into one move to a newer image instead of upgrades nobody can make. operatedBy: provider on a cluster marks one a cloud provider runs, which keeps findings about the parts the provider operates counted in the report and out of the list of work.

How Draugr fits

draugr scan --view actions prints the fix list, one list per kind of work, with the first three rows of each. Without --view actions, a scan lists one finding per row.

$ draugr scan . --view actions

WHAT TO DO  50 actions clear 1154 findings

Bump a dependency  12 actions clear 559 findings
  P1  Upgrade jquery 1.8.3 → 1.12.2
      component storefront · control sca · 4 findings · web/package-lock.json:12 · NSWG-ECO-329 +3
  P1  Upgrade jquery 1.12.2 → 3.5.0  major · beyond policy minor
      component storefront · control sca · 5 findings · web/package-lock.json:12 · and 1 more
  P1  Upgrade jquery 1.8.3 → 3.5.0  major · beyond policy patch
      component api · control sca · 7 findings · app/static/js/jquery.min.js vendored · CVE-2020-11023 +6
  … and 9 actions not listed

Change the code  14 actions clear 23 findings
  P1  Fix “Found user-controlled data used in a system call”
      Semgrep: dangerous-system-call · Bandit: B605
      component api · control sast · 2 findings · app/app.py:16
  ⋯

That is the demo project. In the same list, the row for the python:3.8-slim image clears 520 findings and ranks below these three. All four are P1, and the jquery upgrades to 3.5.0 clear CVE-2020-11023, which is on KEV, while none of the image's findings is. The first row is the step within the storefront's minor policy, ranked with the step after it. vendored marks a copy of a library committed to the tree, which bumping the lockfile leaves as it was. On the code row, two scanners flag the same line under different rules, and one edit clears both.

--top N shows the first N rows of each list and --top 0 shows every one. The MCP server's fix_list tool hands an AI assistant the same lists, and takes kind to return one. The report files keep every finding as its own record either way, so an auditor reading the SARIF sees one per finding. What to fix first in the docs has every grouping rule and key.

Keep learning