📎 Unreleased docs. This is the next version (from main), features here may change or not be in a release yet.See the latest release →

Core concepts

Reducing the noise

On this page

A scan of a real project returns hundreds of findings. Most are true, and few are yours to fix this week. A lever is a descriptor key or a flag that closes that gap, and each one acts at a fixed phase of a run.

This page follows the ledger CVE from Prioritization: a CVSS 7.5 flaw, high, in a Go module that the ledger component depends on. ledger declares exposure: internal and criticality: critical, tier C2, so the CVE ranks at P2 until a lever moves it.

The ledger CVE through each phase of a run with KEV on, from the feeds being read to a later diff

Every lever leaves a record. Ranking keeps every finding and changes its band, removal marks what it set aside and why, and narrowing changes only what is shown. Start by declaring exposure and criticality: without them every band is read as though each component were public and critical.

Ranking

A ranking lever changes a finding's band and keeps every finding in the count. How a band is decided is in Prioritization; the levers here are the ones you pull.

The ledger CVE under four ranking levers: declaring ledger, turning on KEV, --min-priority P1 and --fail-on P1

components:
  - name: ledger
    exposure: internal        # public · authenticated · internal · restricted
    criticality: critical     # critical · important · supporting
leverdoesthe ledger CVEhow it works
exposure, criticalitysets the component's tier, as each finding is rankedP1 unclassified, P2 declaredMatrices
config.exploitabilityturns on ↑ KEV and ↑ EPSS; feeds read at start, applied as each finding is rankedP1 ↑ KEV once listedExploitability
config.dependencyHealthturns on ↑ malicious and ↑ deprecated, after every scanner finishesP1 if its package is flaggedDependency health
config.reachabilityturns on ↓ unreachable, Go only, after every scanner finishesP2 ↓ unreachable, band heldReachability
--min-priority P1lists only P1; counts and the gate still read every findingnot listed at P2--min-priority

The two fields are facts about your architecture and your business, which no scanner can compute, and they sit in the descriptor where a reviewer sees them. A component that declares neither is ranked as public and critical and marked assumed in the report, as Components describes. The feeds come from a local cache that draugr feeds update fills, so a scan never reaches the network for them and the report names the copy it read.

Removal

A removal lever makes the count smaller and keeps each finding it removed in the report, with the reason.

The ledger CVE under each removal lever: correlation, config.exclude, VEX and actions, with the levers that do not reach it

leverdoesappliedthe ledger CVEwhere
correlationone flaw found by several scanners is one row, naming the othersalways on, after every scanner finishesone row, also grype-fsControls & scanners
config.excludeaccepts a finding with a reason and an expiry; the commit that adds the line records whoafter every scanner finishesmarked acceptedSaga schema
VEXimports a supplier's statement that a vulnerability does not affect their productafter every scanner finishesmarked VEXSaga schema
scanner exclusionsa scanner set the finding aside, from a directive in the code or its own configurationas the scanner reportsnone appliesSaga schema
actionsfindings collapse into the change that clears themreport, --view actionsone upgrade of its moduleWhat to fix first
builtBy, operatedBya finding nobody on your team can act on is named as somebody else'sreportnone applies: ledger is yoursWhat to fix first

The three acceptance routes are counted apart in every report. config.exclude is a decision somebody here committed, and the report carries the git history of each exclusion's line as the record of who. VEX is an assertion whose author you can ask. A scanner exclusion was written outside the descriptor with no author and no date, the weakest of the three. It splits again by where it was written, a directive in the scanned file or a rule in the scanner's configuration, because those send you to different people.

A scanner exclusion is counted only when the scanner keeps a record of it. A #trivy:ignore comment in an IaC file and a .gitleaksignore entry leave none, so nothing is counted for them.

An unreachable verdict is not a removal: the ledger CVE stays in the count at P2, marked ↓ unreachable, because a suppression records that a person decided.

Narrowing

A narrowing lever changes what you are shown, never what was found. The counts describe the whole run, and the report says when a listing is filtered.

The ledger CVE under four narrowing levers: --top 2, --view compact, --components ledger and draugr diff

leverdoesappliedthe ledger CVE
--top 0every finding rather than the first tenreportlisted wherever it ranks
--view actionsa row per thing to do instead of a row per findingreportinside its module's upgrade
--view compactone line each, to see how much there isreportone line
--components, --controlsscan one part of the applicationstartfound when ledger and sca are in scope
--exposure, --criticality, --labelsscan the components matching a classificationstartfound when the filter matches ledger
draugr diffonly what a change introduced, against a report from before itafter the runleft out once it is in the earlier report

A pull request that adds no findings needs no report read, whatever the project's standing total, which makes draugr diff the narrowing lever with the largest effect.

Gate

The gate is the threshold that fails a run: any finding at or above it sets the verdict to FAIL and the exit code to non-zero. It reads every finding the run kept, after ranking and removal; narrowing does not change it. A band gate compares a finding's band; a severity gate compares its ranked severity.

settingfails the run on
--fail-on P1, the defaulta finding in P1
--fail-on P2, or config.gate.failOn: P2a finding in P1 or P2
--fail-on critical, or config.gate.failOn: criticala finding whose ranked severity is critical
--fail-on higha finding whose ranked severity is high or critical
config.gate.controlsa threshold per control, in the gate's own vocabulary; a severity there is compared with the ranked severity
--fail-on-new, or config.gate.failOnNewa finding new since --base at or above a band or a ranked severity; this gate alone then decides

The ledger CVE's list, plus one finding the current change introduced:

findingranked severityband--fail-on P1--fail-on P2--fail-on critical--fail-on-new P2
leaked tokenhighP1 ↑ floorfailsfailspassespasses, not new
ledger CVE, KEV offhighP2passesfailspassespasses, not new
ledger CVE, KEV oncriticalP1 ↑ KEVfailsfailsfailspasses, not new
web XSSmediumP2passesfailspassespasses, not new
api CVE, newmediumP2passesfailspassesfails

KEV turns the ledger CVE's verdict under --fail-on critical because the gate reads the ranked severity, not the scanner's high. An unreachable verdict works the other way: an unreachable critical is judged high and passes --fail-on critical. --min-priority leaves every verdict in the table as it is, and a ledger CVE accepted through config.exclude or VEX no longer counts toward any gate. The flags are in scan flags, the descriptor block is config.gate, and how the verdict and exit code are reached is in Verdict & gating.

Marks

A mark is the label on the line under a finding that names what moved its ranking, or, for ↩ history, where the finding came from. Each mark is defined in Signals, with what it moves and the key that turns it on.

markmeans
↑ KEVthe CVE is on CISA's Known Exploited Vulnerabilities catalog
↑ EPSS 0.62the EPSS score, printed to two places, is at or above epssThreshold, default 0.5
↑ maliciousthe package is in the OSSF Malicious Packages Project
↑ deprecatedthe publisher marked the version deprecated
↓ unreachablegovulncheck found no call path to the flaw, Go only
↑ floora leaked credential, ranked at C1 whatever its component declares
↑ minimuma leaked credential, held at high whatever its scanner rated it
↩ historythe finding describes a commit rather than the current tree, so its path may be gone

Counts

A count that dropped because somebody accepted the ledger CVE and one that dropped because its module was upgraded are different facts, and every Draugr report keeps them apart:

  • Accepted findings stay in the report, marked, with the reason and the file and line of the exclusion, whose git history records who wrote it.
  • The three acceptance routes are counted separately, never folded into one total.
  • A rule that suppressed nothing is reported, because an exclusion doing nothing looks exactly like one that is working.
  • An exclusion past its expiry stops suppressing, and the report says so.
  • A filtered listing says it was filtered, and the counts still describe the whole run.
  • The feeds are named with their fetch time, so a verdict that turned on KEV can be checked against the same copy of KEV.