Safeguard
Container Security

OCI image vulnerability scanning explained

A concrete breakdown of how OCI image vulnerability scanning works, where scanners miss real risk, and how to build a scan workflow that doesn't drown teams in noise.

Vikram Iyer
Cloud Security Engineer
7 min read

OCI image vulnerability scanning is the process of inspecting the layers, packages, and metadata inside a container image built to the Open Container Initiative (OCI) format — the specification that Docker, containerd, Kubernetes, and every major registry now use — to find known CVEs before that image reaches production. An image built from node:18-slim can easily bundle 200-400 packages across its layers, and any one of them can carry a disclosed vulnerability, from a stale OpenSSL to a backdoored compression library like the one found in xz-utils (CVE-2024-3094) in March 2024. Scanning works by unpacking the image's layers, generating a manifest of installed packages and versions, and matching that manifest against vulnerability databases such as the NVD, OSV, and vendor advisories. The output is a severity-ranked list of CVEs mapped to specific layers — but a raw list is not the same as a prioritized one, which is where most teams get stuck.

What is OCI image vulnerability scanning?

OCI image vulnerability scanning is the automated inspection of a container image's filesystem layers and package manifests against known-vulnerability databases to surface CVEs before deployment. The "OCI" part matters because it's not a Docker-specific process: the Open Container Initiative, formed under the Linux Foundation in June 2015 and formalized in the Image Format Specification 1.0 (released July 2017), defines the layered tar-based structure that Docker, Podman, containerd, and CRI-O all build and run. A scanner reads the image manifest and config JSON, extracts each layer, and runs package-manager-aware detection (dpkg for Debian/Ubuntu, apk for Alpine, rpm for RHEL/CentOS, plus language-level detection for npm, pip, Maven, and Go modules) to build a Software Bill of Materials. That SBOM — typically in SPDX or CycloneDX format — is then diffed against vulnerability feeds to produce findings, each tagged with a CVE ID, a CVSS score, and the affected package version range.

How does an OCI scanner actually find vulnerabilities inside an image?

An OCI scanner finds vulnerabilities by extracting every layer's file list and package database, then matching installed package-version pairs against structured vulnerability feeds like the NVD, the Open Source Vulnerability database (OSV), and Linux distro security trackers (Debian DSA, Ubuntu USN, Alpine's secdb). Tools such as Trivy, Grype, and Clair all follow this same core loop: pull the image, unpack it (often layer-by-layer to attribute a CVE to the specific RUN instruction that introduced it), enumerate packages, then query a local or remote vulnerability database for matches. The matching step is deceptively hard — a scanner has to know that Debian backports a fix into 1.1.1f-1ubuntu2.16 without bumping the upstream version string, or it will report a false positive for a CVE that was already patched. This is why distro-aware scanners consistently produce fewer false positives than naive version-string comparisons against upstream NVD data alone.

What's the difference between scanning an OCI image and scanning source code?

OCI image scanning inspects what actually ships — the compiled binaries, OS packages, and runtime dependencies baked into layers — while source code (SCA) scanning inspects what's declared in manifest files like package.json or pom.xml before the build happens. The two produce different results because build steps change what lands in the final image: a Dockerfile might install curl and git for a build stage, leave build-time-only dependencies in an intermediate layer, or pull in transitive OS packages that never appear in any application manifest. A 2023-era analysis pattern seen repeatedly in the field is that a source-level scan of a repo's requirements.txt can look clean while the shipped image still carries a vulnerable libxml2 pulled in as an OS-level dependency of a Python wheel. Image scanning also catches vulnerabilities in the base image itself — if a team is still on python:3.9-slim months after a CVE lands in its underlying Debian release, source scanning of the application code alone will never surface that risk.

Which vulnerabilities do OCI scanners typically miss or misjudge?

OCI scanners typically miss vulnerabilities in statically linked binaries, misjudge severity by ignoring whether a vulnerable function is ever called, and lag on zero-days until a CVE ID and database entry exist. Go and Rust binaries are compiled statically, so no package manager entry exists inside the image for a scanner to match against — a vulnerable version of a vendored Go module can sit undetected unless the scanner also parses the binary's embedded module list. Severity is the bigger practical problem: a scanner using raw CVSS will flag a critical-severity CVE in a logging library even if the vulnerable code path is never invoked by the application, which is exactly the pattern behind Log4Shell (CVE-2021-44228, disclosed December 10, 2021, CVSS 10.0) generating alert floods across images that imported Log4j but never used the affected JNDI lookup feature. And timing matters: as of mid-2024, NIST's NVD had a public backlog exceeding 18,000 CVEs awaiting analysis and enrichment, meaning a scanner relying solely on NVD data can show a CVE ID with no severity score or affected-version range for weeks after disclosure.

How often should OCI images be scanned?

OCI images should be scanned at build time on every commit, at push time before a registry accepts the image, and continuously after deployment, because the vulnerability landscape changes even when the image doesn't. A CVE with no public exploit today can get a weaponized proof-of-concept next month — CVE-2024-3094, the xz-utils backdoor, was discovered on March 29, 2024, roughly two years after the malicious commits were first introduced in 2022, meaning any image built and scanned during that window would have shown zero findings for a vulnerability that was already present. Registry-level and runtime rescanning against updated feeds is what catches this class of "sleeper" CVE. In practice this means three distinct scan points: pre-merge (fast, fails the build on new criticals), pre-deploy (full SBOM generation and policy gate), and scheduled rescans of already-running images against the day's updated CVE feed — because an image that was clean on Monday can have a newly disclosed CVE on Tuesday without a single line of its own code changing.

What does a mature OCI scanning workflow look like in CI/CD?

A mature OCI scanning workflow generates an SBOM at build time, gates the pipeline on severity and exploitability rather than raw CVE count, and re-verifies signed images before they're allowed to run. Concretely: the CI job builds the image, a scanner produces a CycloneDX or SPDX SBOM, findings are enriched with EPSS exploit-probability scores (maintained daily by FIRST) rather than CVSS alone, the pipeline fails only on criticals with a meaningful exploitability signal, and the resulting image is signed with a tool like Cosign so the deploy step can verify provenance under a policy modeled on SLSA levels. Teams that skip the exploitability layer end up drowning: a single node:18 image can easily surface 80-150 CVEs across its OS and npm layers, and treating every one as equally urgent trains engineers to ignore the scanner entirely — the same alert-fatigue failure mode that let real risk hide in Log4j-bundling images for months after 2021.

How Safeguard Helps

Safeguard turns raw OCI scan output into a prioritized, actionable queue instead of a wall of CVEs. Reachability analysis determines whether a vulnerable function inside a scanned layer is actually invoked by the application's call graph, so a critical CVE in an unreachable code path — the exact Log4Shell pattern above — gets deprioritized instead of paging someone at 2am. Griffin, Safeguard's AI triage engine, reads the CVE, the package context, and the reachability signal together to explain in plain language why a finding matters (or doesn't) and drafts an auto-fix pull request that bumps the affected package to the first patched version that doesn't break the build. Safeguard also generates SBOMs at build time and ingests existing SPDX/CycloneDX SBOMs from other tools, so teams get one consistent inventory across every OCI image in the registry, scanned on push and rescanned automatically as new CVEs land.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.