Sub-Module 1.3: Vulnerability Scanning & SBOMs
|
This workshop environment has been optimized for learning efficiency. Required tools (cosign v2.4.1, syft v1.42.2, grype v0.109.1), directories, sample applications, and configurations have been pre-installed to focus on core hardened image concepts rather than repetitive setup tasks. |
Introduction
Building secure images is only the first step. In this sub-module, you’ll learn how to analyze and document the security posture of your container images. You’ll scan the hummingbird-demo:v1 image from Sub-Module 1.2 for vulnerabilities and generate Software Bill of Materials (SBOMs) for compliance.
You’ll see firsthand why Red Hat’s "zero-CVE at ship time" approach dramatically reduces your security churn compared to traditional base images.
The tools we’re using in this module use the Podman API to find and inspect images, so we can enable that now for our user.
systemctl --user enable --now podman.socket
Change to a new directory to hold our work for this exercise.
cd ~/scanning
We’ll compare the multi-stage image you built in Sub-Module 1.2 to a pre-built UBI version of the same Quarkus application. This UBI image was built in a single stage and will include additional OS and build tools.
podman images hummingbird-demo
REPOSITORY TAG IMAGE ID CREATED SIZE localhost/hummingbird-demo ubi 4d7953024356 2 minutes ago 623 MB localhost/hummingbird-demo v1 dc4d1b7ef08a 2 hours ago 273 MB
We’ve worked with several different vendors to ensure our CVE feeds are available and supported. In this lab, we’re going to use the open source versions of Anchore’s syft and grype tools as they are easily available. These are already installed on the system.
Grype scans container images for known CVEs. Syft generates Software Bill of Materials (SBOMs) from container images.
Let’s verify they’re ready:
grype version
Application: grype Version: 0.109.1 BuildDate: 2026-03-09T19:35:00Z GitCommit: baf391d5c672e8f409953baa49ea243265866b2c GitDescription: v0.109.1 Platform: linux/amd64 GoVersion: go1.25.8 Compiler: gc Syft Version: v1.42.2 Supported DB Schema: 6
syft version
Application: syft Version: 1.42.2 BuildDate: 2026-03-09T18:26:53Z GitCommit: 75455f050ab028977a6b625165e2ad3644fcc845 GitDescription: v1.42.2 Platform: linux/amd64 GoVersion: go1.26.1 Compiler: gc SchemaVersion: 16.1.3
Vulnerability Scanning with Grype
Step 1: Scan for CVEs
Use Grype to scan your multi-stage image for known CVEs:
grype hummingbird-demo:v1
⠸ Vulnerability DB ━━━━━━━━━━━━━━━━━━━━ [hydrating] ✔ Loaded image hummingbird-demo:v1 ✔ Parsed image sha256:dc4d1b7ef08a1b7e2cdfa2d087005ddf7f4ea8fdba80b51a5634a42eb3eb1629 ✔ Cataloged contents faa8ebd2c0ebecdee5a3576fd94ed4e1f3e9125a2dfbc092ca07a02c003f4bf2 ├── ✔ Packages [169 packages] ├── ✔ File metadata [1,238 locations] ├── ✔ Executables [161 executables] ✔ Scanned for vulnerabilities [0 vulnerability matches] ├── by severity: 0 critical, 0 high, 0 medium, 0 low, 0 negligible └── by status: 0 fixed, 0 not-fixed, 0 ignored No vulnerabilities found
Notice that grype not only examines packages like rpms but also java-archive packages, binaries, and other files and metadata sources. You may see findings from the application dependencies here.
|
hardened base images typically have zero or near-zero CVEs at ship time, so the scan should be clean. |
Step 2: Compare with Full UBI Image
Now scan the UBI version:
grype hummingbird-demo:ubi --only-fixed
|
The |
✔ Scanned for vulnerabilities [29 vulnerability matches] ├── by severity: 2 critical, 25 high, 105 medium, 84 low, 1 negligible (1 unknown) └── by status: 29 fixed, 189 not-fixed, 189 ignored <cut>
Take particular note of the Type column in the output. Most of these are java-archive packages, and pulled in as part of the build process for the UBI based version of our quarkus app. Since we used a single container, and not a multi-stage, all of the build artifacts are inclueded in our production image. This shows how the distroless and minimal approaches can benefit production environments.
|
Key Insight:
The difference is due to unnecessary packages in the UBI image that aren’t needed at runtime but still have security vulnerabilities. |
Knowing what the potential vulnerabilities exist in an image is one part of the equation. How do you document what’s inside once you’ve built that final production image? Enter the SBOM.
SBOM Generation with Syft
Step 3: Generate SBOM
What is an SBOM?
A Software Bill of Materials (SBOM) is a complete inventory of all packages, libraries, and dependencies in your container image. SBOMs are essential for:
-
Supply chain security: Know exactly what’s in your images
-
Compliance: Meet regulatory requirements (NIST, EU Cyber Resilience Act)
-
Vulnerability management: Quickly identify affected systems when new CVEs are disclosed
You can view a human-readable table of packages using the -o flag to select output type.
syft hummingbird-demo:v1 -o table
Noice that syft shows multiple types of packages. The SBOM includes every package in your container:
-
Quarkus runtime and its Java dependencies (Netty, Vert.x, Jackson, SmallRye, etc.)
-
OpenJDK 21 JRE and system RPM libraries from base image (glibc, NSS, etc.)
NAME VERSION TYPE quarkus-rest 3.32.2 java-archive quarkus-rest-jackson 3.32.2 java-archive jackson-databind 2.21.1 java-archive jakarta.ws.rs-api 3.1.0 java-archive netty-common 4.1.130.Final java-archive vertx-core 4.5.25 java-archive ... java-21-openjdk-headless 1:21.0.10.0.7-2.1.hum1 rpm glibc 2.42-10.hum1 rpm nss 3.119.1-1.1.hum1 rpm
For a quick look, a table might be fine, but for compliance, we need something we can publish with the image in a registry. A machine-readable version will let various tools to verify contents, examine properities, and more. There are several formats for machine-readable SBOMs, we’ll use Software Package Data Exchange (SPDX) in this exercise.
syft hummingbird-demo:v1 -o spdx-json=hummingbird-demo.spdx
✔ Loaded image hummingbird-demo:v1 ✔ Parsed image sha256:382ffe2fc7bcb5abe59d02ef463ba41650df18636ddc3107e91518d7cec7ec8f ✔ Cataloged contents a40e8a453bfcbb75409bead5bddb6fcc794c8c34beff3ec9625036713ae22ac5 ├── ✔ Packages [169 packages] ├── ✔ Executables [161 executables] ├── ✔ File digests [1,238 files] └── ✔ File metadata [1,238 locations]
This is JSON so we can inspect elements in the file with jq
jq '.packages | length' hummingbird-demo.spdx
170
|
SBOM Formats: Syft supports multiple output formats:
For compliance requirements, use SPDX-JSON or CycloneDX. |
cd ~
Summary
Congratulations! You’ve completed Sub-Module 1.3 and learned how to analyze container image security.
✅ Scanned images for CVEs with Grype
✅ Generated SBOMs with Syft in SPDX-JSON format
✅ Compared vulnerability counts between Hardened and UBI images
✅ Understood the security benefits of minimal images
Security Benefits:
-
Zero or near-zero CVEs at ship time reduces remediation work
-
56%+ smaller images (273MB vs 623MB) reduce attack surface
-
Complete transparency via SBOMs enables compliance
Why This Matters:
-
Traditional images ship with vulnerabilities that require immediate patching
-
Hardened Images deliver clean baselines, shifting security left
-
SBOMs provide transparency for supply chain security requirements
Tool Integration:
-
Grype: Fast vulnerability scanning with multiple database sources
-
Syft: Multi-format SBOM generation (SPDX, CycloneDX, JSON)
-
Both tools integrate with CI/CD pipelines for automated security gates
Next Steps
In Sub-Module 1.4: Image Signing & Attestation, you’ll learn how to cryptographically sign your images and attach SBOM attestations to prove provenance and integrity.
Troubleshooting
Issue: Grype database download fails
# Manually update database
grype db update
# Check database status
grype db status
Issue: SBOM generation incomplete
# Try different output formats
syft hummingbird-demo:v1 -o json=sbom-detailed.json
# Verify image is accessible
podman images | grep hummingbird-demo
Issue: Podman socket not available
# Restart podman socket
systemctl --user restart podman.socket
# Verify socket is running
systemctl --user status podman.socket