Container image scanning has become a standard pipeline step, and that is progress. But a passing scan is frequently mistaken for a secure image. Scanners compare installed package versions against vulnerability databases — genuinely useful, and blind to configuration, embedded secrets, provenance and everything that happens after the image is built.
What scanners see and miss
A scanner enumerates packages in the image and matches them against known vulnerability data. That catches outdated system libraries and dependencies, which is worth doing on every build.
- It misses configuration — running as root, unnecessary capabilities, or a writable filesystem are not package vulnerabilities.
- It misses secrets — an API key baked into a layer is not a CVE, and remains recoverable from image history even if a later layer deletes the file.
- It misses provenance — whether the image came from where you think, built from the source you think.
- It misses application logic — your own code's vulnerabilities are not in any package database.
- It misses runtime drift — what the container does once running, including anything installed after start.
- It over-reports — many flagged vulnerabilities sit in packages the application never invokes, producing noise that trains teams to ignore results.
Build smaller and cleaner
- Use minimal base images. Fewer packages means a smaller attack surface and dramatically fewer findings to triage.
- Use multi-stage builds so compilers, package managers and build-time credentials never reach the final image.
- Pin base images by digest rather than a floating tag, so builds are reproducible and cannot silently change.
- Rebuild regularly. An image built once and deployed for a year accumulates every vulnerability disclosed in the meantime.
- Never place secrets in build arguments or layers — use build-time secret mounts or inject at runtime.
- Run as a non-root user and declare it in the image rather than relying on the orchestrator to override.
- Remove package managers and shells from production images where practical, which limits what an attacker can do after code execution.
Provenance and the supply chain
The harder question is not whether the image contains known vulnerabilities but whether it is the image you intended to run at all.
A generated software bill of materials records what is actually inside; signing establishes that the image came from your pipeline; and admission control enforces that only signed images from trusted registries can run. Without those, an attacker who compromises a registry or a build step can introduce a malicious image that passes every vulnerability scan cleanly, because nothing in it is a known CVE.
Prioritising findings sensibly
Treating every reported vulnerability as equally urgent guarantees the list is ignored. Severity from the scanner is a starting point, not a decision.
A high-severity vulnerability in a package the application never calls, on a workload with no network exposure, matters far less than a medium-severity issue in an internet-facing component. Reachability, exposure and whether a fix exists all belong in the triage. Publicly maintained exploited-vulnerability catalogues are useful here: known exploitation is a much stronger prioritisation signal than a score alone.
Runtime still matters
Everything above concerns the image. Once running, the container becomes a live workload that can be attacked through the application it hosts.
Runtime controls therefore remain necessary: restricting what the container may do, monitoring for unexpected process execution or outbound connections, and ensuring a compromised container cannot reach the host or the wider cluster.
Related from TechBiz Security
Sources & further reading
- OWASP Docker Security Cheat Sheet
- CISA — Known Exploited Vulnerabilities Catalog
- NIST SP 800-190 — Application Container Security Guide
- Docker — Build best practices
0 comments
Leave a comment