Sub-Module 1.3: Vulnerability Scanning & SBOMs

Sub-Module Overview

Duration: 8-10 minutes
Learning Objectives:

  • Scan container images for CVEs with Grype

  • Generate SBOMs with Syft for compliance

  • Compare Hardened Images vs UBI

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.

Change to a new directory to hold our work for this exercise.

cd ~/scanning

Vulnerability Scanning with Grype

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

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

Step 1: Scan for CVEs

Use Grype to scan your multi-stage image for known CVEs:

grype hummingbird-demo:v1 --by-cve

The scan make take 1-2 minutes to complete. The --by-cve flag normalizes the output to CVE identifers instead of other datasouces like GHSA, where available. This can make comparing to other sources and tools easier.

Expected output:
 ⠸ 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 but may vary from the time of writing depending on known CVEs in any layer. For example, during lab development an issue was found and fixed in netty which is a runtime dependency for the application in both images and was found in both scans.

Step 2: Compare with Full UBI Image

Now scan the UBI version:

grype hummingbird-demo:ubi --by-cve --only-fixed
Expected output (varies, but typically):
✔ Loaded image                                                     hummingbird-demo:ubi ✔ Parsed image  sha256:03dc3b506b2cf65edccead9c049bf460668ec31f0ca8a3b30ac5152a46240946
✔ Cataloged contents   be81f714bea39f05007a9870444cd024bac7d642efbceeb5a2eedf5a696a2c6f
   ├── ✔ Packages                        [991 packages]
   ├── ✔ Executables                     [529 executables]
   ├── ✔ File metadata                   [6,134 locations]
   └── ✔ File digests                    [6,134 files]
 ✔ Scanned for vulnerabilities     [33 vulnerability matches]
   ├── by severity: 2 critical, 34 high, 146 medium, 98 low, 1 negligible (1 unknown)
   └── by status:   33 fixed, 249 not-fixed, 249 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 included in our production image. This shows how the distroless and minimal approaches can benefit production environments.

The --only-fixed flag is another of several filters available and shows only vulnerabilities with available patches. This is useful because it focuses on immediate issues and not wontfix issues set by the distro. To see the full output, run the command again without the flag.

Key Insight:

  • hardened image: 0 CVEs (or very few)

  • UBI image: 15-30+ CVEs (even in a recent version)

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

Red Hat provided SBOMs

Transparency is a key pillar in a secure software supply chain. Each image provided includes a Software Bill of Materials (SBOM) that details the contents. You saw the web version, but that’s not particularly useful for use with security tools.Using cosign you can download the SBOM from the registry directly. Let’s get the SBOM for the openjdk image we used to build the Quarkus app.

cosign download sbom registry.access.redhat.com/hi/openjdk:21-runtime

You will see a warning about using attestations instead of JSON. We’ll go over this concept and the cosign tool in more depth during a later exercise. We just want to see that the SBOM is available.

The JSON displayed will have information about checksums, packages, publishers and more. Our SBOMs are useful to understand the contents of the base, but you’ll likely want to publish SBOMs that include your application and any of it’s dependencies as well. Let’s explore how to do that 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
What’s in the SBOM:

Notice 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.)

Expected output (excerpt):
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 properties, 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
Expected output:
 ✔ 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
Expected output:
166

SBOM Formats:

Syft supports multiple output formats:

  • SPDX-JSON: Standard format for compliance (ISO/IEC 5962:2021)

  • CycloneDX: Alternative SBOM standard

  • Table: Human-readable for quick review

  • JSON: Syft’s native format with detailed metadata

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.

What You’ve Accomplished

✅ 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

Key Takeaways

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