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 | When | What it means |
|---|---|---|
| PASS | syft or `docker sbom` plugin | Either 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. |
| WARN | Only Trivy | No dedicated tool, but trivy is present and will emit CycloneDX or SPDX. Only reachable on a host with no docker binary. |
| WARN | No generator | Nothing 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 --helpexits 0 even when nosbomplugin 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 sbomwas an experimental CLI plugin (Anchore-backed) and is superseded bydocker 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.jsonVerify 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.jsonDebugging
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
- Syft
- Trivy: SBOM generation and scanning
- Docker Scout:
docker scout sbom - Docker: SBOM attestations with BuildKit
- CISA: Software Bill of Materials
- NTIA: Minimum Elements for a SBOM
- OWASP CycloneDX
- SPDX
- NIST SP 800-218 Secure Software Development Framework
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