Supply ChainGENERALTOOL-SPECIFICORG-SPECIFIC

Scanning, and Why a Finding Is Not a Risk

A scanner tells you which known-vulnerable components are present. Whether any of them is exploitable in your system is a separate question, and conflating the two destroys the practice.

The question, the obvious approach, and why it breaks

Every lesson starts where the work starts: an operational problem, a first attempt that is entirely reasonable, and the way production disagrees with it.

The production question

The scan reports 400 findings. Which of them actually matter, and what happens if we treat them all as urgent?

The problem

Scanners are cheap to run and produce far more output than any team can act on. Without a way to prioritise, the output is either ignored entirely or treated as a blocking queue that stops delivery.

What teams do first

Run the scanner in CI and fail the build on any high or critical finding. It is a clear rule, it is easy to explain to an auditor, and it means nothing vulnerable ships.

How it breaks

The build fails on a critical vulnerability in a component the application never invokes, and the fix is a base image upgrade with its own risk. Delivery stops for a change that reduces no real exposure.

How it breaks in production
  • The build fails on a critical vulnerability in a component the application never invokes, and the fix is a base image upgrade with its own risk. Delivery stops for a change that reduces no real exposure.
  • A new critical is published in a widely used base image on a Tuesday and every pipeline in the organisation goes red at once, including the pipeline that would ship the security patch.
  • People learn that going red is normal. Blanket suppressions appear, then a suppression file nobody reviews, and the scanner's output stops being read at all — which is worse than never having scanned.
  • The rule catches the findings that have a CVE and a version match. It says nothing about vulnerable code you wrote, a misconfiguration, or a dependency vendored into a binary where the version is invisible.
  • Severity is a property of the vulnerability, not of your system. The same critical is unexploitable in one service and directly reachable in another, and one blanket rule treats them identically.
CodeBuildTestArtifactReleaseDeployRunObserveOperateIncidentRecoverLearnImprove

What is actually happening

Underneath the tooling, which is the part that survives a change of tool.

  • A scanner builds an inventory of components and versions — from a lockfile, a package database inside an image, or binary analysis — and matches it against vulnerability databases. That is the whole mechanism.
  • It therefore knows presence, not risk. It does not know whether the vulnerable function is called, whether the code path is reachable from untrusted input, whether a compensating control blocks it, or whether the component is even loaded.
  • False positives are structural, not bugs: distribution maintainers backport fixes without changing the version string, vendored copies carry the version of the thing that vendored them, and binary matching is heuristic.
  • Severity scores describe the vulnerability under assumed conditions. Exploit-probability scores estimate how likely exploitation is in the wild. Known-exploited catalogues record what is actually being used. These are three different signals and none of them is your risk.
  • Your risk is a function of exploitability in your deployment: is the path reachable, is the component exposed to untrusted input, what does an attacker gain, and what else would have to fail (Blast Radius: If This Is Wrong, How Much Does It Affect?).

Four signals, and what each one actually tells you

GENERALThe names of the specific scoring systems and catalogues vary by region and vendor, and change over time. The structure — impact model, likelihood estimate, observed-exploitation record, code reachability, deployment exposure — is stable, and it is the structure that does the prioritising.

These get used interchangeably in conversation and they answer different questions. Prioritising well means combining them with the two facts only you have: reachability and exposure.

SignalWhat it tells youWhat it does not
Severity scoreHow bad exploitation would be under the scoring model's assumed conditionsWhether those conditions hold in your deployment
Exploit probabilityA statistical estimate of exploitation in the wild in the near termAnything specific to your system
Known-exploited catalogueThat real exploitation has been observed somewhereThat your configuration is exploitable
Reachability analysisWhether the vulnerable function appears in your call graphWhether an attacker can reach that call with controlled input
Exposure (yours)Whether the component handles untrusted input, and what the workload can reach if compromisedNothing a scanner can supply — you have to know your system

Two policies, and how each one fails

The choice is not between strict and lax. It is between two different failure modes, one of which is visible and one of which is not.

Blocking policy design
Block on all high and critical
every build scans
  -> fails on any high+
    -> 40 findings, most unreachable
      -> release blocked
        -> suppression added to ship
          -> suppression file grows
            -> nobody reads the report

failure: silent, and it looks compliant
Block narrow, triage the rest
every build scans
  -> fails on known-exploited
     or reachable-from-untrusted
    -> usually 0-2 findings, actionable
  -> everything else -> owned queue
     with a target and an expiry
  -> deployed artifacts rescanned daily

failure: loud, and it is a queue you can size

The strict policy optimises for a rule that is easy to state and produces a volume no team can absorb, so it degrades into suppressions — and a suppression is indistinguishable from a decision nobody made. The narrow policy accepts that some findings ship, and buys back far more safety by keeping the report readable and the patch path fast.

Triage in practice

This is what the queue actually looks like when it is working. The trigger column is the scanner's output; everything else is judgement the scanner cannot make.

Scanner says X, the right response is Y
TriggerSymptomCauseResponse
Critical in an OS package inside the imageBuild blocked, package is a shell utility the app never invokesImage-level scan sees everything in the filesystem, invoked or notNot urgent. Fix by shrinking the base image so it is not present at all (What Image Size Actually Costs)
Medium in the request parsing libraryLow severity, low priority in the reportSeverity assigned without knowing it sits on the untrusted input pathTreat as high. Reachable from unauthenticated input is the single strongest prioritisation signal you have
Critical with no fix availableBlocked with no action possibleThe policy assumes a patch always existsCompensating control, documented risk acceptance with an owner and an expiry, and a watch for the fix
Same finding across 60 services60 tickets, one root causeA shared base image or shared libraryFix once at the source and rebuild; ticket the base image, not the services (Golden Paths)
Finding disappears after a rebuild with no dependency changeReport is unstable between runsA floating base image tag changed underneath the buildPin by digest. An unstable inventory makes every other signal untrustworthy (Dependency Pinning)
Known-exploited, in a deployed artifact, reachableOne finding among hundredsThis is the one the whole practice exists forIncident. Patch and ship on the emergency path; the measure is hours (What Happens Between the Page and the Postmortem)

How to do it properly

Most important first.

  • Block the build on a narrow, defensible set: known-exploited vulnerabilities, and anything in a component demonstrably reachable from untrusted input. Everything else goes into a triage queue with a service-level target, not a gate.
  • Scan continuously, not only at build. A vulnerability disclosed after the artifact was built affects a running system that no build will re-examine.
  • Scan the artifact you deploy, not just the source tree. Source-side and image-side scans see different things, and the operating system layer is usually where the volume comes from (Layers and the Build Cache).
  • Every suppression carries a reason, an owner and an expiry. A suppression file with no expiries becomes permanent silently.
  • Reduce the surface rather than triaging it: smaller base images contain fewer components and therefore fewer findings, and the reduction is real rather than administrative (What Image Size Actually Costs, Multi-Stage Builds).
  • Route findings to whoever can fix them, at a volume they can act on. A weekly queue of six that someone owns beats a daily report of four hundred that nobody reads (Alert Fatigue is the same failure).

How much can this affect

Every production change has a blast radius. Stated as a scale so it is comparable between changes rather than adjectival — and paired with what actually contains it, because a wide scope with a real containment mechanism is a different situation from a wide scope with none.

Blast radius if this is wrongEveryone
One testEveryone
What contains it

Nothing in the scanner contains anything. Containment comes from what else is true — network exposure, workload privilege, and how fast you can ship a patch. Scanning tells you where to point those.

What can go wrong

Failure modes, including of the mitigation
  • Scanner fatigue: the output is red so often that red carries no information. This is the dominant failure and it is caused by the blocking policy, not by the scanner.
  • A suppression added to unblock a release, inherited by every subsequent release, hiding a finding that later becomes exploitable.
  • Prioritising by severity alone, so effort goes to a critical in an unreachable code path while a medium in the request-handling path stays open.
  • Scanning the base image and not the application dependencies, or the reverse — the two produce disjoint findings.
  • Treating "no findings" as "not vulnerable". It means the scanner did not match anything, which includes everything it cannot see.
  • A scanner integrated so late that the only response is to ship anyway, which converts the control into documentation of a decision already made.
Misreads this invites
  • "Critical severity means fix it now." Severity is assigned without knowledge of your deployment. A critical in a component you never load is not an emergency; a medium reachable from an unauthenticated endpoint might be.
  • "Zero findings means secure." It means no known-vulnerable versions matched. Logic flaws, misconfiguration and unpublished vulnerabilities produce no findings at all (Security Testing Portfolio in Security Engineering covers what does find them).
  • "The scanner is wrong" as a general dismissal. Some findings are genuinely false, for structural reasons worth understanding. Using that as a blanket explanation is how a real one gets closed.
  • "Blocking the build is the strongest control." The strongest control is the ability to ship a patched artifact within hours. A gate that stops delivery weakens exactly that (Continuous Delivery).

Operating it

Evidence is the signal, not the intention. Rollback is sometimes 'you cannot, and that is the point'.

How you know it worked
  • The number of open findings above your policy threshold is trending down and is small enough to name individually.
  • Time from a known-exploited disclosure to a patched artifact in production is measured, and the measurement is in hours.
  • Every active suppression has an owner and a future expiry date; the count of expired-but-active suppressions is zero.
  • A deliberately vulnerable test artifact is caught by the pipeline, verified rather than assumed.
How you get back
  • Rolling back to the previous artifact usually increases exposure, because the previous artifact has an older component set. Rolling back for a functional reason during a security response needs that stated explicitly.
  • Loosening a scanning gate to unblock delivery is reasonable in an emergency and needs an expiry and an owner attached at the moment it is done, not afterwards.
What to automate, and what stays human
  • Automate: inventory generation, matching, continuous re-scanning of deployed artifacts, routing to owners, and expiring suppressions.
  • Keep human: the decision that a specific finding is not exploitable in your context, and the decision to accept a residual risk. Both are judgements about your system that the scanner has no information about (The Automation Trap).
What this costs
  • A narrow blocking policy means some genuinely exploitable findings ship. A broad one means the output is ignored. There is no setting that avoids both; pick the failure you can detect.
  • Reachability analysis is far more accurate and considerably more expensive, both in tooling and in the time to understand its output.
  • Continuous scanning of deployed artifacts produces findings for things you cannot fix today, which needs a queue and an owner or it becomes noise of a new kind.

Where this applies

This domain is unusually tool- and organisation-dependent. These labels say what each claim is specific to, and what a different platform, provider or organisation does instead.

  • GENERALThe presence-versus-risk distinction holds for every scanner and every ecosystem. What differs is the false-positive rate: lockfile-based matching on an interpreted language is fairly precise, while binary and distribution-package matching is heuristic and noisier.
  • TOOL-SPECIFICScanners differ in what they can see. Some read package manifests only, some read the image filesystem, some attempt reachability analysis of the call graph. Two scanners on the same artifact routinely disagree, which is a property of their inventories rather than evidence that one is broken.
  • ORG-SPECIFICWhere the blocking threshold sits is a policy decision tied to regulatory obligations and appetite. A regulated environment may have to block on severity regardless of exploitability; the response is to make patching fast enough that the policy is affordable, not to argue with it.

Where the depth lives

This domain teaches delivery and operation, and hands the mechanism off to the domain that owns it.

Observability & Performancealert-fatigue
Domains that do not exist yet
  • Testing & Reliability Engineering — reachability as a testing question, and why "is this code path exercised" is the same question a coverage tool answers for a different purpose.