Safeguard
Container Security

Container security throughout the SDLC

A clean build-time scan doesn't mean a secure container. Here's why container security has to span code, build, deploy, and runtime — with real CVE examples.

Michael
Cloud Security Architect
Updated 7 min read

A container image that passes its CI scan on Monday can ship a critical, actively-exploited vulnerability by Friday — not because the scanner failed, but because "secure" was measured once, at build time, and never checked again. The 2021 Codecov bash uploader compromise and the 2024 XZ Utils backdoor (CVE-2024-3094) both showed how a clean base image today becomes a poisoned dependency chain tomorrow. Gartner estimates that by 2025, 90% of organizations deploying containerized applications in production will experience at least one security incident tied to misconfiguration or vulnerable images. Point-in-time scanning at the registry or in a CI pipeline catches maybe one stage of a container's life — build. It says nothing about what happens at deploy, at runtime, or six months later when a new CVE lands against a package that has been sitting untouched in a running pod. Container security has to span the whole software development lifecycle, not one gate in it — which means security in SDLC phases has to be a connected chain of controls, not a single scan you run once and forget.

What does "container security throughout the SDLC" actually mean?

It means applying distinct, connected controls at each of four SDLC stages — code/build, registry/CI, deploy, and runtime — instead of relying on a single scan gate. A Dockerfile with FROM ubuntu:latest and a hardcoded AWS_SECRET_ACCESS_KEY is a code-stage problem; a base image with 40 known CVEs sitting in a registry for 90 days is a build-stage problem; a pod running as root with a mounted Docker socket is a deploy-stage problem; and a cryptomining process spawned inside a "clean" container three weeks after deployment is a runtime-stage problem. Each stage needs its own detective and preventive control, and the findings need to correlate — a CVE flagged at build should be traceable to the exact running workload it affects at runtime, not treated as a separate finding in a separate tool. The 2023 3CX supply chain attack illustrates the gap well: the compromised desktop app passed build-stage checks because the malicious code activated only after a delayed, multi-stage download at runtime — a scenario invisible to any tool that only inspects layers before deployment.

Where do most container vulnerabilities actually get introduced?

Most are inherited from base images and third-party packages, not written by the application team. Analysis of public image registries (Snyk's 2023 State of Open Source Security report, among others) has repeatedly found that official Docker Hub images for popular languages like node, python, and golang ship with dozens to low-hundreds of known CVEs in their OS layer alone — before a single line of application code is added. A team pulling python:3.9 in January 2024 inherited whatever OpenSSL, zlib, and glibc vulnerabilities existed in that Debian snapshot on that day, and that debt compounds every time the image is rebuilt from cache instead of rebased. The second-largest source is transitive dependencies pulled in at build time via pip install, npm install, or apt-get, which is exactly the vector the 2021 ua-parser-js and 2022 node-ipc (the "protestware" incident affecting Ukrainian and Russian IP ranges) supply chain compromises exploited — a single compromised package deep in a dependency tree, baked into an image, and distributed to every downstream consumer without review.

Why isn't a single build-time scan enough?

Because the vulnerability landscape and the running environment both change after the image is built, and a static scan result goes stale immediately. NVD published over 29,000 CVEs in 2023 and continued at a similar pace through 2024 — a package with zero known vulnerabilities at build time can have a critical CVE assigned against it a week later while the image sits unchanged in a registry or a running cluster. The Log4Shell disclosure on December 9, 2021 (CVE-2021-44228) is the canonical example: organizations that had scanned and shipped containers with Log4j 2.14 months earlier had a "clean" scan result on file the day the vulnerability became public, and that scan told them nothing about which of their thousands of running containers actually loaded the vulnerable class. Build-time scanning also can't see runtime behavior — a container that mounts /var/run/docker.sock, runs --privileged, or makes an unexpected outbound connection to a C2 domain looks identical to a benign one in a static image scan.

How much of a typical vulnerability backlog is actually exploitable?

A small fraction — industry data consistently puts reachable, exploitable findings at roughly 5-10% of total reported vulnerabilities in a given codebase or image. A CVE in a compression library that's present in an image but never invoked by any code path the application executes carries functionally zero risk in that deployment, yet it shows up on the same severity-sorted list as a CVE in a networking library that's parsing untrusted input on every request. Teams that triage purely by CVSS score routinely spend the majority of remediation cycles on findings that were never reachable, while a lower-CVSS but network-reachable flaw with a public exploit sits further down the queue. This is precisely why the shift from "count of CVEs found" to "count of CVEs actually reachable from an entry point" has become the dominant framing security teams use to justify engineering time to remediation work.

What controls enforce security in SDLC phases?

At minimum: static Dockerfile/IaC linting and secret scanning at code stage, image and dependency vulnerability scanning with SBOM generation at build stage, admission control and policy enforcement (blocking :latest tags, root users, missing resource limits) at deploy stage, and behavioral/runtime monitoring for process, network, and file-system anomalies at runtime stage. A concrete example: CIS Docker Benchmark checks (e.g., 4.1 "ensure a user for the container has been created") belong at build/deploy stage as policy gates, while detecting a shell spawned inside a container that has never spawned a shell before belongs at runtime stage as anomaly detection — the same underlying risk (unexpected code execution) needs different tooling depending on when it manifests. Kubernetes' own admission controller framework (validating/mutating webhooks, available since Kubernetes 1.9 in 2018) is the standard mechanical hook for enforcing deploy-stage policy, while eBPF-based sensors are now the standard mechanism for low-overhead runtime visibility without instrumenting application code.

How Safeguard Helps

Safeguard closes the gap between build-time findings and runtime reality by running reachability analysis against generated and ingested SBOMs, so a CVE is prioritized based on whether the vulnerable function is actually invoked in your image's runtime code paths — not just present on disk. Griffin AI correlates those reachability signals with exploit intelligence and your deployment topology to cut through severity-score noise and surface the small subset of findings (typically single-digit percentages of total CVEs) that represent real risk. Safeguard generates and ingests SBOMs across your base images, application layers, and CI pipelines to give you one continuous inventory from code to runtime, rather than a fresh, disconnected snapshot every time a scan runs. When a fix exists, Safeguard opens auto-fix pull requests with the minimal version bump or base image change needed to resolve the exploitable finding, so remediation lands as a reviewable PR instead of a backlog ticket. Together, these controls extend visibility across every SDLC stage instead of stopping at the registry gate.

Never miss an update

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