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 scan reports 400 findings. Which of them actually matter, and what happens if we treat them all as urgent?
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.
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.
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.
- 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.
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
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.
| Signal | What it tells you | What it does not |
|---|---|---|
| Severity score | How bad exploitation would be under the scoring model's assumed conditions | Whether those conditions hold in your deployment |
| Exploit probability | A statistical estimate of exploitation in the wild in the near term | Anything specific to your system |
| Known-exploited catalogue | That real exploitation has been observed somewhere | That your configuration is exploitable |
| Reachability analysis | Whether the vulnerable function appears in your call graph | Whether an attacker can reach that call with controlled input |
| Exposure (yours) | Whether the component handles untrusted input, and what the workload can reach if compromised | Nothing 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.
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 compliantevery 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 sizeThe 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.
| Trigger | Symptom | Cause | Response |
|---|---|---|---|
| Critical in an OS package inside the image | Build blocked, package is a shell utility the app never invokes | Image-level scan sees everything in the filesystem, invoked or not | Not urgent. Fix by shrinking the base image so it is not present at all (What Image Size Actually Costs) |
| Medium in the request parsing library | Low severity, low priority in the report | Severity assigned without knowing it sits on the untrusted input path | Treat as high. Reachable from unauthenticated input is the single strongest prioritisation signal you have |
| Critical with no fix available | Blocked with no action possible | The policy assumes a patch always exists | Compensating control, documented risk acceptance with an owner and an expiry, and a watch for the fix |
| Same finding across 60 services | 60 tickets, one root cause | A shared base image or shared library | Fix once at the source and rebuild; ticket the base image, not the services (Golden Paths) |
| Finding disappears after a rebuild with no dependency change | Report is unstable between runs | A floating base image tag changed underneath the build | Pin by digest. An unstable inventory makes every other signal untrustworthy (Dependency Pinning) |
| Known-exploited, in a deployed artifact, reachable | One finding among hundreds | This is the one the whole practice exists for | Incident. 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.
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
- 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.
- "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'.
- 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.
- 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.
- 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).
- 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.
- — 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.