open atlas
↑ Back to track
Docker, containers as a system DOCK · 07 · 02

Image scanning: why a fat base ships a CVE backlog, and what Trivy and Grype actually measure

A fat base (ubuntu/node) carries 300+ OS packages and a long CVE tail you never call; distroless drops that to single digits. Trivy and Grype match package versions against vuln databases — they find known CVEs in your SBOM, not exploitability, so the lever is shrinking the base.

DOCK Senior ◷ 15 min
Level
FoundationsJuniorMiddleSenior

The breach post-mortem named a CVE nobody on the team had ever heard of: a remote-code-execution flaw in a logging library — Log4Shell — that the application did not even use directly. It had been pulled in transitively, baked into a node:18 image months earlier, and shipped in production untouched because nobody had scanned the image after the base was chosen. The kicker came when an engineer finally ran a scanner against that same image: it reported over three hundred packages and dozens of high and critical CVEs, most in OS libraries the service never called — a curl here, a perl module there, a font library that existed only because the base image was a near-complete Debian userland. The vulnerable logging jar was one needle in a haystack the team had been carrying for no reason. The fix was not a frantic CVE hunt; it was rebuilding on a distroless base that shipped the runtime and almost nothing else, which dropped the package count from the three-hundreds into single digits and took the CVE backlog down with it.

What a scanner actually does — and does not

Before you can act on a scan report, you need to understand what the tool is actually measuring — because confusing “reported CVE count” with “risk to your running service” leads to either panic or false confidence.

A container vulnerability scanner like Trivy or Grype is, at its core, a join. It first builds a Software Bill of Materials (SBOM): it walks the image layers and enumerates every installed artifact it can identify — OS packages from the dpkg/rpm/apk database, plus language dependencies from lockfiles like package-lock.json, go.sum, or Gemfile.lock — each with a name and exact version. Then it matches those name+version pairs against a vulnerability database (the NVD plus distro-specific feeds like Debian and Alpine security trackers, GitHub advisories, and so on) and emits every CVE whose affected-version range includes what you shipped. That is the whole mechanism, and its limits follow directly from it: a scanner finds known vulnerabilities in declared versions. It does not know whether the vulnerable code path is ever reached at runtime, whether a fix has been backported by your distro (a frequent source of false positives when the upstream version string looks vulnerable but the distro patched it), or whether anything not in a recognized package manifest — a hand-curl’d binary, a vendored .so — is vulnerable at all. Scanning measures your exposure surface as declared, not your exploitability.

This is why the result is dominated by your base image, not your code. Ship on ubuntu or node and the scanner enumerates the entire base userland: a typical fat base carries 300-plus OS packages, and at any given moment a slice of those have outstanding high or critical CVEs — not because your service is insecure, but because you are carrying a near-complete Linux distribution to run one binary. Most of those packages — shells, package managers, compression libraries, an interpreter or two — are never invoked by your application. They are pure attack surface and pure CVE backlog. The single most effective scan-result reducer is therefore not patching but removing: a distroless base (just glibc, CA certs, and your runtime, with no shell and no package manager) cuts the package count to single digits, and the CVE count collapses with it because there is simply almost nothing left to match against the database.

Quiz

A team's service code has zero known vulnerabilities, but Trivy reports 40 high/critical CVEs against the built image. Where do those CVEs almost certainly come from, and what is the highest-leverage fix?

Why this works

Why not just gate the pipeline on zero CVEs and be done? Because a hard zero-critical gate against a fat base is unmeetable theater — the base will always have some freshly disclosed CVE with no fix yet, so the gate either blocks every deploy or gets bypassed into uselessness. The senior policy separates two things: shrink the base so the count is naturally low (single digits, not hundreds), and gate on fixable criticals with a fixed version available, not on the raw count. A CVE with no upstream fix is a tracking item, not a deploy blocker; a critical with a one-line base bump available is. Counting is cheap; deciding which counts is the work.

Reading severity, fixability, and the SBOM you keep

Scanners bucket findings by severity (CRITICAL/HIGH/MEDIUM/LOW, from CVSS) and, crucially, by fixability — whether a fixed version exists. The number that should drive action is “criticals and highs with a fix available,” because those are the ones a base bump or dependency update actually resolves; the rest are tracked, not chased. Equally important is the artifact the scan produces along the way: the SBOM is worth keeping in its own right. Stored alongside the image (in formats like CycloneDX or SPDX, often only tens to a few hundred kilobytes of JSON), it lets you answer “are we affected by tomorrow’s CVE?” by querying the inventory you already recorded, instead of re-scanning from scratch under incident pressure — which is exactly the question Log4Shell forced every team to answer overnight. Scanning is therefore two outputs: a prioritized, fixability-aware finding list that drives the base-shrink-then-patch loop, and a durable SBOM that turns the next zero-day from a frantic image archaeology dig into a database lookup.

Quiz

What is the most useful way to gate a CI pipeline on scanner results for an image built on a minimal base?

Recall before you leave
  1. 01
    Describe exactly what a scanner like Trivy or Grype does, and three things it cannot tell you.
  2. 02
    Why does the base image dominate scan results, and what is the highest-leverage way to reduce them?
Recap

A container scanner like Trivy or Grype is fundamentally a join: it builds a Software Bill of Materials by enumerating every identifiable installed artifact in the image layers — OS packages from dpkg/rpm/apk and language dependencies from lockfiles, each with a name and exact version — then matches those pairs against vulnerability databases (NVD plus distro and ecosystem feeds) and emits every CVE whose affected range covers a shipped version. Its limits fall straight out of the mechanism: it reports known CVEs in declared versions, not whether the vulnerable path is reachable, not always whether a distro backported the fix (a common false-positive source), and not anything outside a recognized manifest. That is why results are dominated by the base, not your code: a fat base like ubuntu or node carries 300-plus OS packages and a steady tail of high/critical CVEs, most in libraries the service never calls, so a clean app still inherits hundreds of findings. The single highest-leverage reducer is removal, not patching — a distroless base with just glibc, CA certs, and the runtime drops the package count to single digits and the CVE count with it. Gate the pipeline on fixable criticals and highs rather than a raw count, treat unfixable CVEs as tracking items, and keep the SBOM (CycloneDX or SPDX, tens to hundreds of KB) so the next zero-day — the Log4Shell question every team had to answer overnight — becomes a query against an inventory you already recorded instead of a frantic re-scan under incident pressure. Now when you see a scan report with hundreds of findings, your first question is not “which CVE do I patch?” but “what base image am I on, and can I switch to distroless today?”

Practice

Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.

recallapplystretch0 of 5 done

Something unclear?

Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.

shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources3
expand
  1. 01
  2. 02
  3. 03

Trademarks belong to their respective owners. Editorial reference only.