Request change

Docker Scout vs Trivy on DHI

Docker Hardened Images through Docker Scout and Trivy in six different configurations on AWS EC2. Scout reported 0–3 CVEs. Trivy reported 10–62. After finding the official DHI scanning docs and doing the correct 3-step VEX repo setup — both tools converge. Here is every CVE, every suppression reason, every terminal output, and the pipeline config that makes it all work.

Docker Scout vs Trivy on DHI

The Complete Scan Matrix: Four Images, Six Configurations

Before a single explanation — here is the full data from my AWS EC2 test machine. Every number in this table comes from real terminal output you will see in full throughout this post. No “not tested” entries — all six configurations were run across all four images. Screenshot 2026-03-10 at 6.15.28PM.png

🎯 FIVE THINGS HAPPENING SIMULTANEOUSLY — READ THIS FIRST

  1. Default Trivy has zero DHI VEX context. Without configuring the DHI advisory repo, Trivy reports every OS package CVE it finds in NVD. On dev images that is 59 - 62 CVEs. Every single one is a real CVE. Docker just hasn’t told Trivy which ones aren’t exploitable in these specific containers.

  2. --vex repo alone points at the wrong hub entirely. Aqua’s VEX Hub has no entries for DHI images whatsoever. Running with --vex repo but without configuring the DHI source returns identical results to running without any VEX flag. This is the trap everyone including me fell into first.

  3. After correct DHI advisory repo setup, Trivy drops dramatically. 62 becomes 9 on the python image. 59 becomes 9 on traefik. VEX suppresses 50 - 53 CVEs - all glibc, systemd libs, sqlite, ncurses, apt noise. What remains are genuine findings.

  4. OCI VEX file vs advisory repo gives a 1-CVE discrepancy on some images. The --vex vex.json method (exported OCI attestation) suppresses zlib CVE-2026-27171. The --vex repo method (advisory hub) doesn’t have that entry yet. 1-CVE content lag — both methods are valid.

  5. Scout and Trivy have different vulnerability database coverage. Scout finds Python interpreter CVEs (CVE-2025-15367/15366 in Python 3.14.3) that Trivy’s vuln DB completely misses. Trivy scans Go binaries with gobinary. Run both for complete coverage.

DHI: Distroless Philosophy with a Full Signed Attestation Stack

Docker Hardened Images are security-focused base images published at dhi.io. The base tier went Apache 2.0 open source in December 2025 - free to pull without a subscription. The enterprise tier (FIPS 140-2/3, DISA STIG, Extended Lifecycle Support, custom hardening) requires a paid plan. The sfw-ent-dev tag we tested is enterprise: sfw = software supply chain FIPS hardening, ent = enterprise grade.

The core hardening philosophy is ruthless package minimization. The production node:22-debian13 delivers a complete Node.js 22 runtime in 19 packages and 42 MB. The official Docker Hub node:22 image has dozens of CVEs in hundreds of packages. The hardened version eliminates them not by patching but by removing the packages entirely.

The Seven Attestation Layers Shipped With Every DHI Image

Every DHI image ships a complete OCI attestation stack embedded in the manifest - signed JSON documents that any tooling can query programmatically, not just documentation:

Attestation Type Standard / Predicate URI What it proves
SLSA Provenance https://slsa.dev/provenance/v0.2 Cryptographic build chain linking every image to its exact source commit in github.com/docker-hardened-images/definitions. Builder identity, reproducibility proof.
CycloneDX SBOM https://cyclonedx.org/bom/v1.6 Full package inventory with versions, licenses, and dependencies in CycloneDX format.
SPDX SBOM https://spdx.dev/Document Same bill of materials in SPDX format for toolchain compatibility.
OpenVEX ← the key one https://openvex.dev/ns/v0.2.0 Machine-readable CVE exploitability statements — not_affected with signed justification codes, or affected for real findings. This is what makes Scout show 0.
Scout Vulnerability Scan https://scout.docker.com/vulnerabilities Docker’s own scan result at build time. Scout compares against this at runtime.
FIPS Compliance https://docker.com/dhi/fips/v0.1 FIPS 140-2/3 certification evidence. Enterprise FIPS images only.
STIG Scan https://docker.com/dhi/stig/v0.1 DISA STIG assessment with signed OpenSCAP results. Enterprise STIG images only.

Inspect DHI attestations — list all signed claims, export VEX document

# List all attestation predicates on any DHI image
docker scout attestation list dhi.io/node:22-debian13

# Export the OpenVEX document — every suppression decision, signed + timestamped
docker scout vex get dhi.io/node:22-debian13 --output vex.json

# Inspect each CVE's status and justification code
cat vex.json | jq '.statements[] | {
  cve:           .vulnerability.id,
  status:        .status,
  justification: .justification,
  timestamp:     .timestamp
}'

# Example output for the HIGH glibc CVE:
{
  "cve": "CVE-2026-0861",
  "status": "not_affected",
  "justification": "vulnerable_code_not_in_execute_path",
  "timestamp": "2026-02-18T09:14:22Z"
}

Screenshot 2026-03-10 at 6.34.23PM.png

Production vs -dev Variants: The Package Count is the Security Story

Screenshot 2026-03-10 at 6.35.01PM.png

What VEX Is, How It Works, Why Both Tools Need It

VEX - Vulnerability Exploitability eXchange - is a CISA-backed open standard for machine-readable CVE exploitability statements per image. The problem it solves: a CVE existing in a package tells you nothing about whether it is exploitable in your specific container. A vulnerable function compiled out, needing an attack path unavailable in a distroless runtime, or requiring kernel access containers don’t have - none of these are real risks regardless of what NVD says.

VEX lets image vendors attach signed, timestamped, auditable statements documenting exactly why each CVE doesn’t apply. Scanners that consume VEX filter to show only genuinely exploitable findings. Result: instead of an analyst spending 4 hours triaging 62 CVEs, they spend 20 minutes on 9.

Screenshot 2026-03-10 at 6.36.45PM.png

VEX Justification Codes — What “not_affected” Actually Means

VEX statements must include a machine-readable justification code from the OpenVEX spec. Here are the codes you will see in DHI VEX documents:

Screenshot 2026-03-10 at 6.38.16PM.png

Two VEX Delivery Channels: OCI Attestation vs Advisory Repository

Screenshot 2026-03-10 at 6.38.57PM.png

These are the same underlying OpenVEX data published through two channels that serve different scanner architectures. The 1-CVE discrepancy we see on traefik (8 vs 9 CVEs) is because zlib CVE-2026-27171 has a not_affected entry in the OCI attestation but the advisory repo hasn’t published that entry yet - content lag, not a correctness issue.

Official 3-Step Setup — One Time, Then All Scans Work

This is the step missing from every third-party DHI scanning tutorial I found. Do it once on your machine or bake it into your CI runner image, and every subsequent scan applies DHI VEX automatically.

Initialize the Trivy VEX repository config

Creates ~/.trivy/vex/repository.yaml with the default Aqua VEX Hub entry. If the file already exists, prints INFO The configuration file already exists - fine, proceed to step 2.

Add Docker’s DHI advisory repository to the config

Edit ~/.trivy/vex/repository.yaml to add github.com/docker-hardened-images/advisories with enabled: true. This is the step everyone misses. Without it, --vex repo queries only Aqua’s hub which has zero entries for DHI images.

Download VEX data and scan

Run trivy vex repo download to pull Docker’s advisory entries locally. Then scan with --vex repo. Trivy caches and logs No need to check repository updates on subsequent runs.

# ─── STEP 1 ─────────────────────────────────────────────────────────────────
trivy vex repo init
# New install:     INFO [vex] The default repository config has been created
# Already exists:  INFO [vex] The configuration file already exists
# Both are fine. Proceed to step 2.

# ─── STEP 2 ─────────────────────────────────────────────────────────────────
# Edit ~/.trivy/vex/repository.yaml — add the Docker DHI advisory source:
repositories:
  - name: dhi-vex
    url: https://github.com/docker-hardened-images/advisories
    enabled: true

# Or via heredoc (idempotent — safe to run in CI on every job):
grep -q "docker-hardened-images" ~/.trivy/vex/repository.yaml || \
cat >> ~/.trivy/vex/repository.yaml << 'EOF'
  - name: dhi-vex
    url: https://github.com/docker-hardened-images/advisories
    enabled: true
EOF

# ─── STEP 3 ─────────────────────────────────────────────────────────────────
trivy vex repo download
# First run:   INFO [vex] Updating repository... repo="dhi-vex"
# Cached:      INFO [vex] No need to check repository updates repo="dhi-vex"

# ─── NOW SCAN ────────────────────────────────────────────────────────────────
trivy image --scanners vuln --vex repo dhi.io/node:22-debian13
trivy image --scanners vuln --vex repo dhi.io/nginx:1-debian13-dev
trivy image --scanners vuln --vex repo dhi.io/python:3-debian13-sfw-ent-dev
trivy image --scanners vuln --vex repo dhi.io/traefik:3-debian13-dev

# ─── AUDIT TRAIL — see every suppressed CVE ──────────────────────────────────
trivy image --scanners vuln --vex repo --show-suppressed dhi.io/traefik:3-debian13-dev

Screenshot 2026-03-10 at 6.42.03PM.png

Info. --show-suppressed is your compliance audit trail After DHI VEX is configured, Trivy will print: INFO Some vulnerabilities have been ignored/suppressed. Use the "--show-suppressed" flag to display them. Add --show-suppressed to see every CVE that was filtered by VEX along with the source advisory entry and status code. Essential for SOC2, ISO27001, or CRA compliance documentation - you can prove exactly which CVEs were assessed and why they were dismissed.

node:22-debian13 — Scout 0, Trivy 10 → 0 After Setup

The hardened Node.js 22 production image is the clearest story. Scout shows 0. Trivy shows 10 without VEX context and 0 after correct setup. Every single one of those 10 CVEs lives in exactly one package - libc6. Not Node.js. Not npm. Not any application code. Just glibc.

terminal

$ docker scout cves dhi.io/node:22-debian13

    ✓ SBOM obtained from attestation, 19 packages found
    ✓ Provenance obtained from attestation
    ✓ VEX statements obtained from attestation   ← reads OCI manifest layer, zero config needed
    ✓ No vulnerable package detected

                    │                    Analyzed Image
    ────────────────┼──────────────────────────────────────
     Target         │  dhi.io/node:22-debian13
     digest         │  sha256:a34b9c1d7f2e...
     platform       │  linux/amd64
     provenance     │  github.com/docker-hardened-images/definitions
     vulnerabilities│    0C     0H     0M     0L
     size           │  42 MB
     packages       │  19         ← full Node.js 22 runtime in just 19 packages

Screenshot 2026-03-10 at 6.43.40PM.png

The third line is the entire story: ✓ VEX statements obtained from attestation. Scout pulled the signed OpenVEX document from the OCI manifest, applied all not_affected statements for the glibc CVEs, and reported the post-suppression result. The 0 is not hiding CVEs - it is applying a cryptographically signed, auditable security assessment you can inspect yourself with docker scout vex get.

$ trivy image --scanners vuln dhi.io/node:22-debian13

INFO  Detected OS  family="debian" version="13.3"
INFO  [debian] Detecting vulnerabilities...  pkg_num=12
WARN  Using severities from other vendors for some vulnerabilities.

Total: 10 (UNKNOWN: 0, LOW: 7, MEDIUM: 2, HIGH: 1, CRITICAL: 0)

│ libc6  │ CVE-2026-0861    │ HIGH   │ glibc: Integer overflow in memalign            │
│        │ CVE-2025-15281   │ MEDIUM │ glibc: wordexp WRDE_REUSE|WRDE_APPEND uninit   │
│        │ CVE-2026-0915    │ MEDIUM │ glibc: info disclosure via network query        │
│        │ CVE-2010-4756    │ LOW    │ glibc: glob CPU/memory exhaustion (wont-fix)    │
│        │ CVE-2018-20796   │ LOW    │ glibc: regex uncontrolled recursion             │
│        │ CVE-2019-1010022 │ LOW    │ glibc: stack guard page bypass                  │
│        │ CVE-2019-1010023 │ LOW    │ glibc: ldd execution on malicious ELF           │
│        │ CVE-2019-1010024 │ LOW    │ glibc: ASLR bypass via thread cache             │
│        │ CVE-2019-1010025 │ LOW    │ glibc: heap address info disclosure             │
│        │ CVE-2019-9192    │ LOW    │ glibc: regex deep recursion (same as 20796)     │

Screenshot 2026-03-10 at 6.47.56PM.png

$ trivy image --scanners vuln --vex repo dhi.io/node:22-debian13

INFO  [vex] No need to check repository updates  repo="default"  ← Aqua hub only, zero DHI entries
INFO  Detected OS  family="debian" version="13.3"  pkg_num=12

Total: 10 (UNKNOWN: 0, LOW: 7, MEDIUM: 2, HIGH: 1, CRITICAL: 0)
# Identical to no-VEX scan. --vex repo flag did nothing. Expected — no DHI data in Aqua hub.

Screenshot 2026-03-10 at 6.48.47PM.png

$ trivy image --scanners vuln --vex repo dhi.io/node:22-debian13

INFO  [vex] No need to check repository updates  repo="dhi-vex"  ← DHI advisory repo active
INFO  Detected OS  family="debian" version="13.3"  pkg_num=12
INFO  Some vulnerabilities have been ignored/suppressed. Use the "--show-suppressed" flag.

Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
# All 10 glibc CVEs suppressed by DHI VEX. Matches Docker Scout result.

# With --show-suppressed you can see all 10 suppressed entries:
$ trivy image --scanners vuln --vex repo --show-suppressed dhi.io/node:22-debian13
│ libc6  │ CVE-2026-0861    │ HIGH   │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2025-15281   │ MEDIUM │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2026-0915    │ MEDIUM │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2010-4756    │ LOW    │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2018-20796   │ LOW    │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2019-1010022 │ LOW    │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2019-1010023 │ LOW    │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2019-1010024 │ LOW    │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2019-1010025 │ LOW    │ not_affected │ vulnerable_code_not_in_execute_path │
│        │ CVE-2019-9192    │ LOW    │ not_affected │ vulnerable_code_not_in_execute_path │

Screenshot 2026-03-10 at 6.49.27PM.png

nginx-dev — Scout 1 Real CVE, Trivy 60 → 9 After Fix

The nginx dev image is where the story gets more interesting. 107 packages. Full shell. The entire Debian system toolset. Scout applies VEX and still reports 1 CVE — because the VEX status on that CVE is affected, not not_affected. Docker deliberately didn’t suppress it. It’s genuine.

$ docker scout cves dhi.io/nginx:1-debian13-dev

    ✓ SBOM obtained from attestation, 107 packages found
    ✓ Provenance obtained from attestation
    ✓ VEX statements obtained from attestation
    ✗ Detected 1 vulnerable package with 1 vulnerability

                    │                    Analyzed Image
     Target         │  dhi.io/nginx:1-debian13-dev
     digest         │  d2dcc93f95bc
     platform       │  linux/amd64
     provenance     │  github.com/docker-hardened-images/definitions
                    │  1116d8212377c9efec79facff5e0c9066a2e6b13
     vulnerabilities│    0C     0H     0M     1L
     size           │  26 MB
     packages       │  107

## Packages and Vulnerabilities
   0C  0H  0M  1L  util-linux 2.41-5
   pkg:deb/debian/util-linux@2.41-5
   ✗ LOW CVE-2025-14104
     https://scout.docker.com/v/CVE-2025-14104
     Affected range : <=2.41-5
     Fixed version  : not fixed

Screenshot 2026-03-10 at 6.51.46PM.png

VEX was applied. Everything suppressable was suppressed. One CVE survived: CVE-2025-14104 in util-linux 2.41-5, VEX status affected. This is exactly what the scanner should do — it is not hiding it, it is surfacing it. The -dev image includes the full util-linux binary set and the vulnerable setpwnam() code path is reachable.

─── METHOD 1: No VEX (or --vex repo with default Aqua hub) ──────────────────
$ trivy image --scanners vuln dhi.io/nginx:1-debian13-dev
INFO  Detected OS  family="debian" version="13.3"  pkg_num=71
Total: 60 (UNKNOWN: 1, LOW: 51, MEDIUM: 6, HIGH: 2, CRITICAL: 0)
│ libc-bin       │ CVE-2026-0861    │ HIGH   │ glibc memalign integer overflow     │
│ libc6          │ CVE-2026-0861    │ HIGH   │ (same CVE — libc6 package)          │
│ libc6          │ CVE-2025-15281   │ MEDIUM │ glibc wordexp uninit memory         │
│ libsqlite3-0   │ CVE-2025-7709    │ MEDIUM │ SQLite FTS5 integer overflow         │
│ zlib1g         │ CVE-2026-27171   │ MEDIUM │ zlib DoS CRC32 infinite loop        │
│ util-linux     │ CVE-2025-14104   │ LOW    │ setpwnam() heap buffer overread      │
│ libblkid1      │ CVE-2025-14104   │ LOW    │ (same CVE — sub-package)            │
│ libmount1      │ CVE-2025-14104   │ LOW    │ (same CVE — sub-package)            │
│ libuuid1       │ CVE-2025-14104   │ LOW    │ (same CVE — sub-package)            │
│ tar            │ CVE-2005-2541    │ LOW    │ 21yr old Debian wont-fix            │
│ ... 50 more LOW CVEs across glibc (×7), systemd libs (×6), apt, perl-base... │

─── METHOD 2: --vex vex.json (OCI attestation exported) ─────────────────────
$ docker scout vex get dhi.io/nginx:1-debian13-dev --output vex.json
    ✓ Found 1 VEX attestation for image → vex.json written
$ trivy image --scanners vuln --vex vex.json dhi.io/nginx:1-debian13-dev
Total: 9 (UNKNOWN: 0, LOW: 9, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
# zlib CVE-2026-27171 SUPPRESSED here — OCI VEX has not_affected for it

─── METHOD 3: --vex repo (DHI advisory repo configured) ─────────────────────
$ trivy image --scanners vuln --vex repo dhi.io/nginx:1-debian13-dev
INFO  [vex] No need to check repository updates  repo="dhi-vex"
Total: 9 (UNKNOWN: 0, LOW: 8, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
│ bash          │ TEMP-0841856-B18BAF │ LOW    │ Debian TEMP priv esc                │
│ libblkid1     │ CVE-2025-14104      │ LOW    │ util-linux setpwnam()               │
│ liblastlog2-2 │ CVE-2025-14104      │ LOW    │ (sub-package)                       │
│ libmount1     │ CVE-2025-14104      │ LOW    │ (sub-package)                       │
│ libsmartcols1 │ CVE-2025-14104      │ LOW    │ (sub-package)                       │
│ libuuid1      │ CVE-2025-14104      │ LOW    │ (sub-package)                       │
│ tar           │ TEMP-0290435-0B57B5 │ LOW    │ tar rmt side effects TEMP           │
│ util-linux    │ CVE-2025-14104      │ LOW    │ (source package)                    │
│ zlib1g        │ CVE-2026-27171      │ MEDIUM │ zlib DoS CRC32 (advisory hub open)  │

Screenshot 2026-03-10 at 6.53.29PM.png

Python Enterprise Dev — Scout Finds CVEs Trivy Misses, 62 → 9 After Setup

This image produces the most instructive comparison because it exposes a blind spot in Trivy, not just a configuration gap.

$ docker scout cves dhi.io/python:3-debian13-sfw-ent-dev

    ✓ SBOM obtained from attestation, 104 packages found
    ✓ Provenance obtained from attestation
    ✓ VEX statements obtained from attestation
    ✗ Detected 2 vulnerable packages with a total of 3 vulnerabilities

     vulnerabilities│    0C     0H     2M     1L
     size           │  74 MB      packages │ 104
     digest         │  9cfcdf3957aa
     provenance     │  github.com/docker-hardened-images/definitions
                    │  4c9767819e2e7647999e29d36a6e2d26d1b49fd2

## Package 1: python 3.14.3  (pkg:dhi/python@3.14.3)
   ✗ MEDIUM CVE-2025-15367
     Affected range : <3.15.0-alpha6    Fixed version: 3.15.0-alpha6
   ✗ MEDIUM CVE-2025-15366
     Affected range : <3.15.0-alpha6    Fixed version: 3.15.0-alpha6

## Package 2: util-linux 2.41-5
   ✗ LOW CVE-2025-14104
     Affected range : <=2.41-5    Fixed version: not fixed

Screenshot 2026-03-10 at 6.54.33PM.png

$ trivy image --scanners vuln dhi.io/python:3-debian13-sfw-ent-dev

INFO  Detected OS  family="debian" version="13.3"  pkg_num=71
INFO  [python-pkg] Detecting vulnerabilities...
# pip-26.0.1.dist-info/METADATA → 0 vulnerabilities (Trivy scans pip packages, not the interpreter)

Total: 62 (UNKNOWN: 1, LOW: 53, MEDIUM: 6, HIGH: 2, CRITICAL: 0)
# No Python CVEs. 62 CVEs are all OS packages. Same as the --vex repo with Aqua hub.

Screenshot 2026-03-10 at 6.55.14PM.png

$ trivy image --scanners vuln --vex repo dhi.io/python:3-debian13-sfw-ent-dev

INFO  [vex] No need to check repository updates  repo="dhi-vex"  ← DHI repo active
INFO  Detected OS  family="debian" version="13.3"  pkg_num=71
INFO  Some vulnerabilities have been ignored/suppressed.  ← VEX working — 53 CVEs suppressed

Total: 9 (UNKNOWN: 0, LOW: 8, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
# 62 → 9 — VEX suppressed 53 CVEs

│ bash          │ TEMP-0841856-B18BAF │ LOW    │ bash priv esc (Debian TEMP)         │
│ libblkid1     │ CVE-2025-14104      │ LOW    │ util-linux setpwnam() heap overread  │
│ liblastlog2-2 │ CVE-2025-14104      │ LOW    │ (util-linux sub-package)            │
│ libmount1     │ CVE-2025-14104      │ LOW    │ (util-linux sub-package)            │
│ libsmartcols1 │ CVE-2025-14104      │ LOW    │ (util-linux sub-package)            │
│ libuuid1      │ CVE-2025-14104      │ LOW    │ (util-linux sub-package)            │
│ tar           │ TEMP-0290435-0B57B5 │ LOW    │ tar rmt side effects (Debian TEMP)  │
│ util-linux    │ CVE-2025-14104      │ LOW    │ (source package)                    │
│ zlib1g        │ CVE-2026-27171      │ MEDIUM │ zlib DoS CRC32 infinite loop        │
# Note: CVE-2025-15367 and CVE-2025-15366 (Python interpreter) not in Trivy's vuln DB at all

Screenshot 2026-03-10 at 6.56.07PM.png

Info. Trivy misses the 2 Python 3.14.3 CVEs that Scout correctly catches CVE-2025-15367 and CVE-2025-15366 are vulnerabilities in the Python 3.14.3 interpreter binary itself, fixed in 3.15.0-alpha6. Scout surfaces them via its intelligence database and the DHI SBOM which tracks pkg:dhi/python@3.14.3. Trivy's python-pkg scanner targets installed packages from pip/poetry/requirements — it scans pip-26.0.1.dist-info/METADATA which reports 0 CVEs because pip itself is clean. The interpreter binary is out of scope for that scanner. This is the fundamental reason to run both tools: complementary database coverage finds different things.

Traefik Dev — 554 Packages, Two Trivy Methods, 1-CVE Gap Explained

The traefik dev image is the most complex case: 554 packages, the traefik binary scanned as a Go binary via gobinary, and two Trivy VEX methods giving slightly different results revealing a content lag in the advisory hub.

$ docker scout cves dhi.io/traefik:3-debian13-dev

    ✓ SBOM obtained from attestation, 554 packages found
    ✓ Provenance obtained from attestation
    ✓ VEX statements obtained from attestation
    ✗ Detected 1 vulnerable package with 1 vulnerability

     vulnerabilities│    0C     0H     0M     1L
     size           │  108 MB     packages │ 554
     digest         │  0d0cc3b053b1
     provenance     │  590b46daa696447f3d70a096e46e516257a844ed

   0C  0H  0M  1L  util-linux 2.41-5
   pkg:deb/debian/util-linux@2.41-5
   ✗ LOW CVE-2025-14104    Fixed version: not fixed
   # traefik binary (usr/local/bin/traefik — gobinary): 0 CVEs — Go binary is clean

Screenshot 2026-03-10 at 6.58.03PM.png

$ docker scout vex get dhi.io/traefik:3-debian13-dev --output vex.json
    ✓ SBOM obtained from attestation, 554 packages found
    ✓ Found 1 VEX attestation for image
    ✓ Report written to vex.json

$ trivy image --scanners vuln --vex vex.json dhi.io/traefik:3-debian13-dev

INFO  Detected OS  family="debian" version="13.2"  pkg_num=67
INFO  [gobinary] Detecting vulnerabilities...
INFO  Some vulnerabilities have been ignored/suppressed.  ← OCI VEX file applied

Total: 8 (UNKNOWN: 0, LOW: 8, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
# Report Summary:  usr/local/bin/traefik (gobinary): 0 CVEs ← Go binary is clean

│ bash          │ TEMP-0841856-B18BAF │ LOW  │ bash priv esc Debian TEMP           │
│ libblkid1     │ CVE-2025-14104      │ LOW  │ util-linux setpwnam()               │
│ liblastlog2-2 │ CVE-2025-14104      │ LOW  │ (sub-package)                       │
│ libmount1     │ CVE-2025-14104      │ LOW  │ (sub-package)                       │
│ libsmartcols1 │ CVE-2025-14104      │ LOW  │ (sub-package)                       │
│ libuuid1      │ CVE-2025-14104      │ LOW  │ (sub-package)                       │
│ tar           │ TEMP-0290435-0B57B5 │ LOW  │ tar rmt side effects TEMP           │
│ util-linux    │ CVE-2025-14104      │ LOW  │ (source package)                    │
# zlib1g CVE-2026-27171 ← SUPPRESSED — OCI VEX marks it not_affected

Screenshot 2026-03-10 at 6.58.44PM.png

terminal

$ trivy vex repo download
INFO  [vex] Updating repository...  repo="dhi-vex"
           url="https://github.com/docker-hardened-images/advisories"

$ trivy image --scanners vuln --vex repo dhi.io/traefik:3-debian13-dev

INFO  [vex] No need to check repository updates  repo="dhi-vex"
INFO  Some vulnerabilities have been ignored/suppressed.

Total: 9 (UNKNOWN: 0, LOW: 8, MEDIUM: 1, HIGH: 0, CRITICAL: 0)
# usr/local/bin/traefik (gobinary): 0 CVEs

│ bash          │ TEMP-0841856-B18BAF │ LOW    │ bash priv esc TEMP                  │
│ libblkid1     │ CVE-2025-14104      │ LOW    │ util-linux setpwnam()               │
│ liblastlog2-2 │ CVE-2025-14104      │ LOW    │ (sub-package)                       │
│ libmount1     │ CVE-2025-14104      │ LOW    │ (sub-package)                       │
│ libsmartcols1 │ CVE-2025-14104      │ LOW    │ (sub-package)                       │
│ libuuid1      │ CVE-2025-14104      │ LOW    │ (sub-package)                       │
│ tar           │ TEMP-0290435-0B57B5 │ LOW    │ tar rmt TEMP                        │
│ util-linux    │ CVE-2025-14104      │ LOW    │ (source package)                    │
│ zlib1g        │ CVE-2026-27171      │ MEDIUM │ zlib DoS CRC32 (advisory repo open) │
# ↑ 1 extra vs OCI VEX method: advisory repo hasn't published not_affected for zlib yet

Screenshot 2026-03-10 at 6.59.26PM.png

Info. 🔬 8 vs 9 explained: OCI VEX is ahead of the advisory repository The OCI-embedded VEX (read by Scout, exportable via docker scout vex get) marks zlib CVE-2026-27171 as not_affected - Docker assessed it at image build time and embedded the decision. The DHI advisory repository at github.com/docker-hardened-images/advisories publishes entries asynchronously and hasn't published this specific entry yet. Both are Docker-authored data. It is content lag, not a correctness difference. For conservative scanning use --vex repo. For the most complete VEX suppression including build-time assessments, use --vex vex.json.

The 7 Remaining CVEs: What Each One Is and What to Do

Screenshot 2026-03-10 at 7.00.50PM.png Screenshot 2026-03-10 at 7.04.09PM.png Screenshot 2026-03-10 at 7.04.44PM.png

Scout vs Trivy vs Grype: Complete DHI Support Comparison

Screenshot 2026-03-10 at 7.05.57PM.png

Recommendation: Scout + Trivy together for DHI

Scout finds application-layer CVEs that Trivy misses (Python 3.14.3 interpreter CVEs). Trivy scans Go binaries comprehensively, supports IaC/secrets scanning, and works fully air-gapped after initial setup. After the one-time DHI repo config, both tools produce consistent OS package results. The combination covers blind spots neither tool has alone.

Production-Ready GitHub Actions Pipeline for DHI Scanning

name: DHI Security Scan
on: [push, pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:

        # Cache Trivy VEX data — avoids GitHub API rate limits per run
        - name: Cache Trivy VEX data
        uses: actions/cache@v4
        with:
            path: ~/.trivy/vex
            key: trivy-vex-${{ runner.os }}-${{ env.CACHE_WEEK }}
        env:
            CACHE_WEEK: ${{ steps.date.outputs.week }}

        # One-time Trivy DHI VEX setup (idempotent — safe in every run)
        - name: Setup Trivy DHI VEX configuration
        run: |
                trivy vex repo init
                grep -q "docker-hardened-images" ~/.trivy/vex/repository.yaml || \
                cat >> ~/.trivy/vex/repository.yaml << 'EOF'
          - name: dhi-vex
            url: https://github.com/docker-hardened-images/advisories
            enabled: true
        EOF
                trivy vex repo download

        # Hardened production image — fail pipeline on any HIGH/CRITICAL after VEX
        - name: Scan hardened image (production gate)
        run: |
                trivy image \
            --scanners vuln \
            --vex repo \
            --exit-code 1 \
            --severity HIGH,CRITICAL \
            --show-suppressed \   # log suppressed CVEs for audit trail
            dhi.io/node:22-debian13

        # Scout scan — catches Python interpreter CVEs Trivy misses
        - name: Docker Scout scan (application layer coverage)
        uses: docker/scout-action@v1
        with:
            command: cves
            image: dhi.io/python:3-debian13-sfw-ent-dev
            only-severities: critical,high,medium
            exit-code: true

        # Dev images — fail only on HIGH/CRITICAL, accept known LOWs
        # Known: CVE-2025-14104 util-linux LOW (no fix), bash/tar TEMPs
        - name: Scan dev image (informational)
        run: |
                trivy image \
            --scanners vuln \
            --vex repo \
            --exit-code 1 \
            --severity HIGH,CRITICAL \
            --show-suppressed \
            dhi.io/traefik:3-debian13-dev

Every Command From This Post — Copy-Paste Ready

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 ONE-TIME SETUP — Trivy with DHI advisory repository (official)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
trivy vex repo init
# Add to ~/.trivy/vex/repository.yaml:
  - name: dhi-vex
    url: https://github.com/docker-hardened-images/advisories
    enabled: true
trivy vex repo download

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 DOCKER SCOUT — zero config, reads OCI attestation automatically
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
docker scout cves dhi.io/node:22-debian13
docker scout cves dhi.io/nginx:1-debian13-dev
docker scout cves dhi.io/python:3-debian13-sfw-ent-dev
docker scout cves dhi.io/traefik:3-debian13-dev

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 TRIVY — after DHI repo configured above
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
trivy image --scanners vuln --vex repo dhi.io/node:22-debian13
trivy image --scanners vuln --vex repo dhi.io/nginx:1-debian13-dev
trivy image --scanners vuln --vex repo dhi.io/python:3-debian13-sfw-ent-dev
trivy image --scanners vuln --vex repo dhi.io/traefik:3-debian13-dev
trivy image --scanners vuln --vex repo --show-suppressed dhi.io/traefik:3-debian13-dev

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 OCI VEX FILE — air-gapped / per-image approach
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
docker scout vex get dhi.io/traefik:3-debian13-dev --output vex.json
trivy image --scanners vuln --vex vex.json dhi.io/traefik:3-debian13-dev
grype dhi.io/traefik:3-debian13-dev --vex vex.json

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
 AUDIT — inspect every suppression decision
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
docker scout attestation list dhi.io/node:22-debian13
docker scout vex get dhi.io/node:22-debian13 --output vex.json
cat vex.json | jq '.statements[] | {id:.vulnerability.id,status,justification}'
# Browse: https://github.com/docker-hardened-images/advisories

Screenshot 2026-03-10 at 7.07.35PM.png

Share
Like this post?

Request a change or update

Suggest a correction or content update. The post author or an admin will be notified and can resolve or respond.

Comments (0)

No comments yet. Be the first to share your thoughts.

Leave a comment