80%
of container image vulnerabilities reported by scanners are in the OS base layer -- packages the application team did not choose and cannot directly update
3 layers
that every container image contains and that require different scanning and remediation ownership: OS base layer, OS application packages, and application-level dependencies
60%
of teams that integrate container scanning with fail-on-any-critical thresholds disable or bypass the scanner within 3 months because the pipeline failure rate makes it operationally untenable
EPSS 0.1
is a practical minimum exploitation probability threshold for blocking pipeline failures -- findings below this level represent theoretical risk present in code that is rarely targeted in practice

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

The container image scanner configured to fail the CI pipeline on any CVSS 7.0 or above finding will fail on the first run in almost every environment that has not already done extensive base image hardening. The reason: official Docker Hub images for Node.js, Python, Java, and most other runtimes contain dozens of OS-level packages with known CVEs that have not been patched in the base image -- sometimes because no fix exists yet, sometimes because the vulnerability is in a component the runtime never uses, and sometimes simply because the base image maintainer has not released an update.

The team that runs this scanner configuration for the first time sees 150 findings, tries to triage them, realizes most are in the OS layer they do not directly control, and decides the scanner is generating too much noise to be useful. The scanner gets bypassed or disabled.

This guide covers how to configure container image scanning with thresholds that surface genuine risk without blocking builds on findings the team cannot fix, how to distinguish OS-layer findings from application-layer findings, and how to establish a process where scanner failures are trusted signals rather than background noise.

The Three Layers of a Container Image and Who Owns Each

Every container image has three vulnerability layers with different ownership and different remediation paths:

Layer 1: The OS base image. The FROM directive in the Dockerfile pulls an official image (ubuntu:22.04, debian:bookworm-slim, alpine:3.19, or a runtime-specific image like node:20-alpine). The OS base image contains the operating system, package manager, core utilities, and an inherited set of installed packages -- including vulnerabilities in those packages. The application team chose which base image to use but does not maintain it. Remediation requires updating to a newer version of the base image or switching to a more minimal base image (Alpine vs. Debian, for example).

Layer 2: OS packages installed in the Dockerfile. Any RUN apt-get install, RUN apk add, or equivalent command installs additional OS packages that inherit vulnerabilities from their package versions. The application team owns these choices -- if the Dockerfile installs curl, bash, or git for build-time tasks and those packages have CVEs, the team can remove them from the final image or use multi-stage builds to separate build-time tools from the runtime image.

Layer 3: Application-level dependencies. The npm packages, Python pip packages, Maven/Gradle dependencies, or Go modules that the application imports. These are directly controlled by the application team through package.json, requirements.txt, pom.xml, or go.mod. Vulnerabilities in this layer are the same as standard SCA findings in the application's dependency tree.

Effective scanner configuration treats these three layers differently: Layer 1 findings route to a base image update process, Layer 2 findings are application team responsibility with a remediation path of package removal or version update, and Layer 3 findings integrate with the application's existing SCA workflow.

Threshold Configuration That Catches Real Risk Without Blocking Everything

Scanner threshold configuration determines when a scan result fails the pipeline versus when it produces an advisory report that does not block the build.

A threshold configuration that produces trusted results and does not get disabled:

Fail the build on: findings in Layer 2 (OS packages the application team installed) or Layer 3 (application dependencies) with CVSS 9.0 or above AND EPSS above 0.1 (10% exploitation probability). This combination targets findings that are both theoretically severe and actively targeted in practice.

Warn but do not fail on: findings in Layer 2 or Layer 3 with CVSS 7.0 to 8.9, or CVSS 9.0 with EPSS below 0.1. These appear in the pipeline output and in the scan report but do not block deployment. The team sees them and can schedule remediation without being blocked from shipping.

Suppress and report separately on: Layer 1 (base image) findings. These are surfaced in a separate base image vulnerability report routed to whoever owns the base image selection and update process -- typically a platform or infrastructure team, not the application development team.

Grype and Trivy both support this configuration. Trivy supports severity filtering (--severity CRITICAL) and EPSS threshold filtering with the --ignore-unfixed flag (which skips findings with no available fix). Grype supports custom ignore rules in .grype.yaml that can target specific packages, package types, or CVEs. Snyk Container provides reachability filtering similar to its SCA product for application-layer dependencies.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Managing Base Image Vulnerabilities Without Blocking Application Teams

OS base image vulnerabilities are the largest source of scanner noise and the least actionable for application development teams. The fix is almost always "update the base image to a newer version" -- a change that is the development team's responsibility but depends on the base image maintainer publishing an update.

Three strategies for managing base image vulnerability volume:

Pin to minimal base images. The larger the base image, the more packages it contains, and the more vulnerability surface it carries. Switching from node:20 (Debian-based, ~300MB) to node:20-alpine (Alpine-based, ~50MB) typically reduces the package count from 100+ to under 30, reducing scanner findings by 60 to 80%. Alpine uses musl libc and busybox instead of the full GNU toolchain, which also reduces the historical CVE count significantly.

Use distroless base images where possible. Google's distroless images contain only the runtime and its direct dependencies -- no shell, no package manager, no utilities. The result is near-zero OS-layer scanner findings because there are almost no OS packages to have vulnerabilities. Distroless images are available for Java, Python, Node.js, and Go. Caveat: no shell means debugging and interactive access are not possible in distroless containers, which requires tooling changes for incident response.

Automate base image updates. Dependabot, Renovate, and similar tools support automated pull requests for base image updates when a newer version is available. Configuring weekly automated base image update PRs means the development team always uses a recent base image without requiring manual monitoring of upstream changes.

The Allow-List Approach for Known-Accepted Findings

Even well-configured thresholds produce some scanner failures for findings the team has evaluated and accepted -- a CVE with no available fix, a vulnerability in a library feature the application never uses, or a finding in the base image layer that cannot be addressed until the upstream base image publishes a fix.

Both Trivy and Grype support ignore files that suppress specific findings from the scan results. Trivy uses a .trivyignore file at the repository root. Grype uses a .grype.yaml with an ignore section. Each suppressed finding entry should include: the CVE ID, the reason for suppression, the date added, and an expiration date (at which point the entry is automatically reconsidered).

The expiration date is the key discipline mechanism. Suppress a finding indefinitely and it becomes background noise that hides real risk over time. Suppress it for 90 days and the team is forced to re-evaluate it when the suppression expires: has a fix been published? Has the EPSS score changed? Is the finding still in the application?

The review process for expiring suppressions:

  • Query the allow-list for entries expiring in the next 30 days
  • For each entry: confirm the CVE is still present in the image, check whether a fix is now available, check whether the EPSS score has changed since suppression
  • Either renew the suppression with an updated justification or remove it and treat the finding as active

This process prevents allow-lists from becoming permanent suppression archives that accumulate over time without review.

Implementing Scanning in the Pipeline: Placement and Tooling

Container image scanning should run at two points in the CI/CD pipeline:

Pre-push scan (in the build job). Run the scanner against the locally built image before it is pushed to the registry. This catches findings at the earliest point and prevents vulnerable images from entering the registry. The scan runs as a pipeline step after the docker build command and before docker push. For GitHub Actions: use the aquasecurity/trivy-action or anchore/scan-action with the image-ref parameter pointing to the locally built image. For GitLab CI: use the container_scanning template or a custom trivy job in the pipeline YAML.

Registry scan (continuous, separate from the build pipeline). Run a scheduled scan against all images in your container registry at least daily. New CVEs are published daily -- an image that was clean last week may have a critical finding this week without any code change. AWS ECR Enhanced Scanning (using Inspector), Google Artifact Registry Vulnerability Scanning, and Azure Container Registry scanning all support continuous registry scanning. These scans produce findings that are routed to the image owner (identified by the image tag or repository name convention) rather than blocking any pipeline.

Scan the final runtime image, not the build image. In multi-stage Dockerfiles, the first stage may contain build tools (compilers, test frameworks, build-time dependencies) and the final stage contains only the runtime application. Scan the final stage image -- the one that runs in production. Scanning the build stage generates findings for tools that never reach production and creates unnecessary noise.

For Kubernetes deployments: integrate admission control scanning (using Trivy Operator or similar) that scans images at pod creation time against the current CVE database, catching vulnerabilities in images that were clean when built but have since had new CVEs published against them.

The Escalation Policy for Blocked Builds

Even with well-calibrated thresholds, pipeline blocking scan failures will occur. The escalation policy determines what happens next and prevents both "block everything indefinitely" and "approve everything immediately without review."

A three-path escalation policy:

Path 1: Fix and resubmit (for Layer 2 and Layer 3 findings with available fixes). If the scanner failure is for a finding in a package the application team controls (OS package they installed, application dependency) and a fix is available (updated package version), the expected resolution is: update the package, rebuild the image, resubmit the pipeline. SLA for this path: critical findings blocking a release must be resolved within 24 hours; high severity within 5 business days.

Path 2: Temporary suppression with documented exception (for findings with no available fix or base image findings incorrectly classified by the scanner). If the finding has no available fix or is a base image finding that exceeded the threshold due to scanner miscategorization, the engineer opens an exception request with: the CVE ID, the reason a fix is not currently available, the compensating control (network isolation, input validation, feature flag that disables the vulnerable code path), and the review date. A security team member approves the exception. The finding is added to the allow-list with the expiration date from the exception request.

Path 3: Emergency deployment override (for production incidents where a delayed fix is available). If a production incident requires deploying a build that has a scanner failure and the fix requires more than 24 hours, the pipeline includes an override mechanism requiring dual approval (engineering manager plus security team member) with a mandatory post-deployment remediation ticket created at the time of override. This is the emergency exit that prevents a security scanner from blocking a P0 incident response while maintaining accountability for the vulnerability.

The bottom line

Container image scanning fails when it is configured to block builds on every finding regardless of layer ownership, exploitation probability, or fix availability. It succeeds when threshold configuration distinguishes OS-layer findings (report separately, route to base image owners) from application-layer findings (block on high EPSS criticals, warn on others), the allow-list process handles known-accepted findings with expiration dates rather than indefinite suppressions, and the escalation policy gives teams a clear path to resolution without blocking legitimate deployments indefinitely. The scanners that stay enabled and trusted are the ones that generate failures engineers believe represent real risk -- not the ones that fail every build until the team learns to ignore them.

Frequently asked questions

Why does my container image scanner fail the build on the first run?

Official container base images (node, python, debian, ubuntu) contain OS-level packages with known CVEs that have not been patched in the current base image -- either because no fix exists yet, because the vulnerability is in a component the runtime does not use, or because the base image maintainer has not released an update. A scanner configured to fail on any CVSS 7.0 or above finding will fail immediately on most base images. The fix is a threshold configuration that distinguishes between OS base layer findings (which the application team does not own) and application-layer findings (which the team controls), and only blocks the pipeline on high-exploitation-probability findings in the layers the team owns.

What scanner threshold should I use to avoid blocking every build?

A threshold that produces trusted results: fail the build on Layer 2 (OS packages the application team installed) or Layer 3 (application dependencies) findings with CVSS 9.0 or above AND EPSS above 0.1 (10% exploitation probability). Warn but do not fail on CVSS 7.0 to 8.9 findings or CVSS 9.0 findings with EPSS below 0.1. Suppress and report separately on Layer 1 (base image) findings, routing them to whoever owns base image selection and updates. This configuration catches actively exploited severe vulnerabilities while preventing the scanner from failing on the large volume of theoretical OS-layer findings.

What is the difference between Trivy, Grype, and Snyk Container?

All three scan container images for known CVEs across the OS layer and application layer, but they differ in features and integration model. Trivy (open source, Aqua Security) is the most widely used standalone scanner with GitHub Actions support, Kubernetes operator mode, and ignore file configuration. Grype (open source, Anchore) has flexible custom ignore rule configuration in .grype.yaml and integrates with the Anchore Enterprise platform. Snyk Container is a commercial tool that provides reachability analysis for application-layer dependencies (similar to Snyk's SCA product) and integrates with Snyk's license and policy management. Trivy or Grype are the standard choice for teams without existing Snyk contracts; Snyk Container adds value for teams already using Snyk for SCA.

How do I handle base image vulnerabilities my application team cannot fix?

Three strategies reduce base image vulnerability volume: switch to minimal base images (node:20-alpine instead of node:20 reduces the package count from 100+ to under 30 and cuts scanner findings by 60 to 80%), use distroless base images where possible (no shell, no package manager, near-zero OS-layer findings), and automate base image updates via Dependabot or Renovate (weekly automated PRs for new base image versions keep the team on recent builds without manual monitoring). For base image CVEs with no available fix, use the allow-list with a 90-day expiration date and route base image findings to a platform team rather than the application development team.

Where in the CI/CD pipeline should container image scanning run?

At two points: pre-push (in the build job, after docker build and before docker push) to catch findings before the vulnerable image enters the registry, and continuous registry scanning (daily, separate from the build pipeline) to catch new CVEs published against images already in the registry without requiring a code change. For multi-stage Dockerfiles, scan the final runtime image -- not the build stage that contains compilers and build tools that do not reach production. For Kubernetes deployments, add Trivy Operator or equivalent admission control scanning to catch vulnerabilities in images that were clean when built.

How do I handle a container image scanner blocking a production deployment?

A three-path escalation policy handles scanner blocks without either blocking indefinitely or approving everything: for findings with available fixes, update the package and resubmit within the defined SLA (24 hours for criticals blocking a release). For findings with no available fix, open a documented exception request with CVE ID, unavailability justification, compensating control, and review date -- a security team member approves and the finding goes on the allow-list with an expiration date. For genuine production emergencies, an override mechanism with dual approval (engineering manager plus security team) creates a mandatory post-deployment remediation ticket. All three paths maintain accountability while preventing the scanner from being the blocker that gets disabled.

What is an allow-list in container image scanning and how should I manage it?

An allow-list (Trivy: .trivyignore file, Grype: ignore section in .grype.yaml) suppresses specific CVE findings from scan results. Each entry should include the CVE ID, suppression reason, date added, and an expiration date (typically 90 days). The expiration date forces periodic re-evaluation: has a fix been published? Has the EPSS score changed? Is the finding still present in the image? Without expiration dates, allow-lists accumulate indefinitely and become suppression archives that hide real risk. Review expiring entries monthly and either renew with updated justification or remove the suppression and treat the finding as active.

Sources & references

  1. Trivy: Container Image Scanning Documentation
  2. Anchore Grype: Vulnerability Scanner Documentation
  3. NIST: Container Security Guide SP 800-190
  4. Snyk Container: Documentation
  5. Google: Artifact Registry Vulnerability Scanning

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.