Traditional vulnerability scanners were built for hosts, physical or virtual machines with an operating system you can query directly. They install agents, talk to APIs, and interrogate the running system. Containers break all of those assumptions. Your scanner can't install an agent in a 5MB distroless image. It can't query a container that's already terminated. And it has no visibility into the 47 layers that make up your python:3.11-slim base image.
Container security requires a different scanning model, a different remediation workflow, and a different way of thinking about what "patched" even means in an ephemeral, immutable infrastructure world.
libssl in your Ubuntu 22.04 base image can affect every container in your cluster that inherits from that base. A single patch (rebuilding and redeploying the base image) closes all of them simultaneously. But finding that single CVE requires SBOM-level analysis, not host-level scanning.
Why Container CVEs Are Different: Layer Inheritance and Base Image Sprawl
A Docker image is a stack of read-only layers. When you write a Dockerfile, you typically start with FROM ubuntu:22.04 or FROM python:3.11-slim. That base image contains an entire Linux userspace, libc, OpenSSL, curl, apt, and dozens of other packages, each of which can have CVEs.
Your application layers sit on top. They add your runtime, your dependencies, your application code. But every CVE in every layer below yours is also in your image, and therefore in every container spawned from that image.
The sprawl problem emerges from two patterns:
- Multiple base images: Different teams choose different bases, some use
debian:bookworm-slim, others usealpine:3.19, others useubuntu:22.04. Each base has its own CVE profile. You end up with dozens of unique base images to track. - Pinned but never updated: Teams pin base images to specific digests (
ubuntu:22.04@sha256:abc123) to ensure reproducibility. But a pinned image from 8 months ago has 8 months of accumulated CVEs. The image doesn't self-update.
Triaging CVEs: Base Image Layer vs. Application Layer
The first triage question for any container CVE is: which layer does it live in? This determines the remediation path entirely.
Base Image Layer CVEs
These are in system packages (libc6, openssl, libssl3, curl, etc.) that came from the base image. Remediation requires updating the base image and rebuilding all images that inherit from it. You don't need to change application code. The fix is a base image bump in your Dockerfile's FROM line, followed by a full rebuild and redeploy.
Application Layer CVEs
These are in your declared dependencies. Python packages in requirements.txt, npm modules in package-lock.json, Java libraries in pom.xml, Go modules in go.sum. Remediation requires updating the dependency version in your manifest, then rebuilding. This may or may not require code changes depending on the nature of the update.
The Exploitability Question
Container CVEs have a higher false-positive rate than host CVEs because many vulnerabilities only affect functionality that a container doesn't use. A CVE in OpenSSL's server-side TLS implementation might be present in a container that only makes outbound HTTP connections and never listens on a TLS socket. The vulnerability exists in the binary but is functionally unexploitable in that container's context.
Tools like Grype and Trivy are starting to incorporate VEX (Vulnerability Exploitability eXchange) data to flag this, but as of 2026, this analysis is still largely manual. The heuristic: base image CVEs affecting networking libraries in containers that don't handle network-facing workloads are lower priority than the CVSS score suggests.
SBOM Generation with Syft and Grype
An SBOM (Software Bill of Materials) is a machine-readable inventory of every package in your container image, including versions, package manager origin, and layer attribution. It's the foundation of container-level vulnerability scanning.
Generating SBOMs with Syft
# Install Syft
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin
# Generate SBOM for a local image in SPDX JSON format
syft myapp:latest -o spdx-json > myapp-sbom.spdx.json
# Generate SBOM for an image in your registry
syft registry:myregistry.io/myapp:v1.2.3 -o spdx-json > myapp-sbom.spdx.json
# Generate SBOM in CycloneDX format (required for some compliance frameworks)
syft myapp:latest -o cyclonedx-json > myapp-sbom.cdx.json
# Generate SBOM including layer attribution (shows which Dockerfile layer added each package)
syft myapp:latest -o json | jq '.artifacts[] | {name, version, locations}'
Scanning SBOMs with Grype
# Scan a local image directly
grype myapp:latest
# Scan from a previously generated SBOM (much faster, no image pull required)
grype sbom:myapp-sbom.spdx.json
# Output only CVEs with a fix available (reduces noise significantly)
grype myapp:latest --only-fixed
# Output as JSON for pipeline integration
grype myapp:latest -o json > myapp-vulns.json
# Filter by severity (only Critical and High)
grype myapp:latest --fail-on high
# Scan with VEX data to suppress known false positives
grype myapp:latest --vex myapp.vex.json
--fail-on critical to block deploys of images with critical unpatched CVEs. Store the SBOM as a build artifact alongside the image digest. This creates an auditable record of the vulnerability state at build time for compliance purposes.
Kubernetes Namespace Isolation and CVE Risk
In a Kubernetes cluster, namespace isolation affects how severely a container CVE can be exploited if an attacker does gain code execution. A CVE that enables container escape (breaking out to the host) is dramatically more serious than one enabling lateral movement within the same namespace.
Key isolation controls that affect CVE severity assessment:
- Pod Security Standards (PSS): Enforce
restrictedpolicy in production namespaces. Prevents privileged containers, host network/PID/IPC access, and privilege escalation, which blocks the most common container escape techniques. - Network Policies: Deny egress by default; explicitly allow required outbound connections. A container CVE enabling SSRF or code execution is dramatically limited if the container can't make arbitrary outbound connections.
- Service Account Permissions: Containers with an overprivileged service account (e.g., cluster-admin) turn any code execution CVE into a full cluster compromise. Scope service account permissions to the minimum required.
- Seccomp and AppArmor profiles: Restrict the system calls a container can make. A kernel CVE that requires specific syscalls (e.g.,
unsharefor namespace attacks) is blocked by a tight seccomp profile.
Image Signing with Cosign
Image signing ensures that the container image running in your cluster is the exact image that passed your security scanning pipeline, not a tampered version, not an image pulled from a different registry, not an older version with known vulnerabilities.
# Install Cosign
brew install cosign # macOS
# or
curl -O -L https://github.com/sigstore/cosign/releases/latest/download/cosign-linux-amd64
# Generate a key pair (or use keyless signing with OIDC in CI)
cosign generate-key-pair
# Sign an image after it passes your security scan
cosign sign --key cosign.key myregistry.io/myapp:v1.2.3
# Verify signature before deployment (can be integrated into admission controllers)
cosign verify --key cosign.pub myregistry.io/myapp:v1.2.3
# Attach SBOM as a signed attestation
cosign attest --key cosign.key --predicate myapp-sbom.spdx.json \
--type spdx myregistry.io/myapp:v1.2.3
# Verify SBOM attestation
cosign verify-attestation --key cosign.pub --type spdx \
myregistry.io/myapp:v1.2.3
In Kubernetes, enforce signed images using the Sigstore Policy Controller or OPA/Gatekeeper admission webhook. Any image without a valid signature is rejected before it can run.
CVE Remediation vs. Image Rebuild: When to Do What
The container remediation decision tree differs from traditional patching:
- CVE in base image OS package + fix available upstream: Update the
FROMline to a newer digest (or uselatestwith pinning on the updated digest). Rebuild all dependent images. This is the most common case and the most mechanical to automate. - CVE in base image + no fix available yet: Evaluate whether you can switch to an alternative base image (e.g., from
ubuntu:22.04todebian:bookworm-slimwhere the package version is already patched). If not, document the risk acceptance until a fix ships upstream. - CVE in application dependency + fix available: Update the dependency version in your manifest, run tests, rebuild. Use Dependabot or Renovate to automate PR creation for these updates.
- CVE in application dependency + no fix: Evaluate alternative packages, apply mitigating controls, or fork and patch. This is the highest-effort remediation path.
Automation with Dependabot and Renovate
Manual image updates don't scale. In a polyglot microservices environment with 50+ services, you need automated PR creation for dependency updates.
Dependabot for Container Images (GitHub Actions)
# .github/dependabot.yml
version: 2
updates:
# Monitor Python package dependencies
- package-ecosystem: "pip"
directory: "/services/api"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
# Monitor Docker base images
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
labels:
- "dependencies"
- "container"
Renovate for More Complex Pinning Strategies
// renovate.json
{
"extends": ["config:base"],
"packageRules": [
{
"matchPackagePatterns": ["^ubuntu", "^debian", "^alpine", "^python"],
"matchDepTypes": ["stage"],
"groupName": "base images",
"schedule": ["every weekend"]
},
{
"matchDepTypes": ["dependencies"],
"matchUpdateTypes": ["patch"],
"automerge": true,
"automergeType": "pr"
}
],
"vulnerabilityAlerts": {
"enabled": true,
"labels": ["security"]
}
}