Docker audit fix

How to Generate an SBOM for Docker Container Images

An SBOM lists every package inside a container image so the next CVE announcement becomes a lookup instead of a re-scan of everything. Generate one per image with syft in CycloneDX or SPDX format and store it alongside the release artefacts.

Hiren KalariyaLast reviewed: Oct 12, 2026Check sbom
Low
severity
No
needs root or sudo
3
results it can return
8 of 8
checks in this audit

Every threshold on this page is transcribed from the docker-security-image-vulnerabilities audit script that CtrlOps runs, and a build check fails if the two ever disagree. See all 8 Image Vulnerabilities checks, or how the audit runs.

Low severityNo sudo needed

What this check reads

The check probes for tooling on PATH in a fixed order and stops at the first hit: syft present is a PASS, otherwise a docker binary whose docker sbom --help succeeds is a PASS, otherwise a trivy binary is a WARN because it can emit an SBOM without being a dedicated tool, and none of the three is the final WARN. There is no other threshold and no numeric one. It never runs a generator, never looks for an SBOM file on disk and never inspects an image, which is also why it has no SKIP branch.

The second probe does not work as intended. The Docker CLI answers --help on a command it does not know by printing its general help and exiting 0, so docker sbom --help succeeds whether or not the plugin exists. On any host with a docker binary the check therefore stops there and PASSes, and the two WARN branches are only reachable on a host with no Docker at all.

When it applies

Always runs - it needs neither Docker nor root, and never SKIPs. It is purely a tooling-availability check: is syft on PATH, does docker sbom --help work, is trivy present. It never generates an SBOM, looks for one on disk, or inspects an image. A PASS therefore means "a generator is installed", not "SBOMs exist, are current, or are stored anywhere" - a host with syft installed and no SBOM ever produced passes exactly like one with a complete, published inventory.

What each result means

Result thresholds this check applies
ResultWhenWhat it means
PASSsyft or `docker sbom` pluginEither syft is installed, or a docker binary exists. The plugin probe succeeds on any Docker host, so this is not evidence that a generator is present, let alone that an SBOM was ever produced.
WARNOnly TrivyNo dedicated tool, but trivy is present and will emit CycloneDX or SPDX. Only reachable on a host with no docker binary.
WARNNo generatorNothing on this host can produce an SBOM. Also only reachable on a host with no docker binary.

Why it matters

A Software Bill of Materials (SPDX or CycloneDX) records every package in an image so that when a new CVE appears (Log4Shell, xz) you can answer "are we affected?" from inventory rather than rescanning everything. US Executive Order 14028 and NTIA minimum elements; CISA SBOM guidance; OWASP CycloneDX; NIST SP 800-218 (SSDF) PS.3.2.

Why it fails, and when it is wrong

  • The plugin probe passes on every Docker host (known issue). docker sbom --help exits 0 even when no sbom plugin is installed, because the CLI prints its general help for an unknown command. A host with Docker and no SBOM tool of any kind gets a PASS.
  • docker sbom was an experimental CLI plugin (Anchore-backed) and is superseded by docker scout sbom; the check does not look for Scout. Trivy's SBOM output (trivy image --format cyclonedx) is production quality, so the WARN for "only Trivy" is conservative.
  • Presence of a tool is not the same as SBOMs being produced and stored; the check cannot verify that.
  • BuildKit can attach an SBOM attestation at build time (docker buildx build --sbom=true).

How to fix it

Generate one per image

syft <image> -o cyclonedx-json=sbom.json          # or -o spdx-json=sbom.json
trivy image --format cyclonedx --output sbom.json <image>

Either is a sound choice. docker scout sbom <image> is the current Docker-native route; the old docker sbom plugin is deprecated.

Store it with the release

Keep one SBOM per image digest beside the release artefacts, or have BuildKit attach it to the image as an attestation at build time:

docker buildx build --sbom=true --provenance=true -t <registry>/app:1.0 --push .

Re-check it when advisories drop

This is where the value is: a stored SBOM that nobody re-scans provides no security benefit.

grype sbom:./sbom.json
trivy sbom ./sbom.json

Verify the fix

command -v syft; docker sbom --help >/dev/null 2>&1 && echo 'docker sbom available'

# Actually produce one, which is what the check cannot confirm:
syft <image> -o cyclonedx-json=sbom.json
jq '.components | length' sbom.json          # expected: a realistic component count

# Close the loop - an SBOM is only useful if it is matched against advisories:
grype sbom:./sbom.json
trivy sbom ./sbom.json

Debugging

The expected misunderstanding. The check only verifies a generator exists. Generate them per image in CI (syft <image> -o cyclonedx-json), store them with the release artefacts, and re-scan the stored SBOMs when new CVEs appear.

A mild finding: trivy will do the job (trivy image --format cyclonedx --output sbom.json <image>). Installing syft clears it, but using trivy is a perfectly good answer.

Install syft, or accept the finding if you generate SBOMs in a build pipeline on another host, which this check cannot see.

The known issue above. The check tests the plugin with docker sbom --help, which succeeds on any host with Docker, plugin or not. Run docker sbom with no arguments: without the plugin it fails with an unknown command error. Check for a real generator with command -v syft trivy.

The plugin was distributed with Docker Desktop and is deprecated; on a Linux server use syft directly rather than relying on it.

The value comes from re-checking stored SBOMs against new advisories (grype sbom:./sbom.json). Generating them and filing them away provides no security benefit.

Sources

How the script reads this

Next

Re-run the Image Vulnerabilities audit after applying the fix and confirm this check moves to PASS. CtrlOps runs all 8 checks over your existing SSH connection and scores the result, so the change is visible without reading another config file.

All 8 Image Vulnerabilities fixes
Audit your fleet

Find every one of these on every server, in one click

CtrlOps runs this audit over your existing SSH connection - no agents, no scripts to manage. $7/user/month after a 1 month free trial - no credit card required.

Windows

✓ Start instantly·✓ No credit card·✓ No sneaky autorenewals