skip to content

A Trivy 0.74 CI step runs `trivy image --format cyclonedx --output sbom.cdx.json --exit-code 1 --severity CRITICAL app:1.4` and never fails — why?

level: middleimportance: should knowfreq 22%

answer

  1. the format changes what runs
  2. an INFO line at start-up
  3. the scanner list is emptied
  4. only an explicitly set --scanners survives

basics

~20 s

An SBOM output format (cyclonedx, spdx or spdx-json) empties Trivy's scanner list unless --scanners was set explicitly, so the run only inventories packages. With no findings, --exit-code has nothing to trigger on; add --scanners vuln.

solid answer

~40 s

In Trivy 0.74 an SBOM output format switches scanning off by default. When `--format` is `cyclonedx`, `spdx` or `spdx-json` and `--scanners` has not been set, either on the command line or as `scan.scanners` in `trivy.yaml`, Trivy logs at INFO that the format "disables security scanning", clears the scanner list and only builds the package inventory. With no vulnerability or secret findings, `--exit-code 1` never fires and the gate stays green. The fix is `--scanners vuln`, after which CycloneDX also embeds the vulnerabilities. The other options are two separate runs, one to gate and one to write the SBOM, or one JSON scan followed by `trivy convert`.

code

bash · 1 line
bash
trivy image --scanners vuln,secret --format cyclonedx --output sbom.cdx.json --exit-code 1 --severity CRITICAL registry.example.internal/shop/app:1.4

go deeper

for a junior

Remember that an SBOM output format on its own does not scan for vulnerabilities in Trivy; you add --scanners vuln when you want findings.

for a middle

Explain the mechanism: the three SBOM formats clear the scanner list unless the setting is explicit, on the flag or in trivy.yaml, so the exit code never triggers.

for a senior

Show how you would prove a gate works: a known-vulnerable control image, a log check for the disable message, and explicit scanners instead of a shared config default.

for a principal

Discuss why one pipeline step should not both archive evidence and gate releases, and how you would standardise the step so that teams cannot silently lose the gate.

## The rule in Trivy 0.74.0 Trivy decides which **scanners** run (vulnerability, misconfiguration, secret, license) from `--scanners`, which defaults to `vuln,secret` for `trivy image`. One rule overrides that default. When `--format` is one of the three SBOM formats, `cyclonedx`, `spdx` or `spdx-json`, and the scanners setting was **not set anywhere**, Trivy: - logs at INFO level: `"--format cyclonedx" disables security scanning. Specify "--scanners vuln" explicitly if you want to include vulnerabilities in the "cyclonedx" report.`; - clears the scanner list; - enables only the internal package-inventory step that the SBOM needs. "Set anywhere" covers the `--scanners` flag and the `scan.scanners` key in `trivy.yaml`. The reasoning behind the rule is that an SBOM is an inventory: by default `--format cyclonedx` describes components and does not include vulnerabilities. ## What the broken gate looks like 1. The job runs, pulls the image, analyses the layers and writes a valid `sbom.cdx.json`. 2. The one log line that explains the behaviour is an INFO message among many. 3. No scanner produced a finding, so `--exit-code 1` has nothing to act on and the step exits 0. 4. The default secret scanner is switched off as well, so the job also stopped looking for credentials baked into the image. The step looks healthy and the artifact it uploads looks healthy too. Nothing goes red until someone asks why a critical CVE shipped. ## Ways to fix it | Option | Shape | Trade-off | |---|---|---| | Explicit scanner | add `--scanners vuln` (or `vuln,secret`) | one run; the CycloneDX file now carries a vulnerabilities section, and `--severity` also limits what is written into it | | Two runs | `trivy image --exit-code 1 --severity CRITICAL ...` then `trivy image --format cyclonedx --output ...` | the gate and the archive are independent; the second run reuses the layer cache | | Scan once, convert | `--format json --output result.json`, then `trivy convert --format cyclonedx` | one analysis; the SBOM and the gate come from the same result | For SPDX the same rule applies. With `--scanners vuln` the scan runs and the exit code works, but SPDX 2.3 has no place for vulnerabilities, so only CycloneDX carries them inside the document. ## Why an explicit setting is the durable fix - **Config drift.** If a shared `trivy.yaml` sets `scan.scanners`, every job using it keeps scanning with an SBOM format. Removing that key months later silently turns scanning off in every job that relied on it. - **Readability.** `--scanners vuln` on the command line states the intent where a reviewer will read it. - **Scope.** Keep the scanners you actually want. Turning on `vuln` alone still leaves secret scanning off in this job. ## A walk-through of the failing job A team adds SBOM generation to its existing scan step, reasoning that one invocation is cheaper than two. Before the change the step was `trivy image --exit-code 1 --severity CRITICAL app:1.4`, and it failed whenever a critical CVE appeared. After the change the same step also carries `--format cyclonedx --output sbom.cdx.json`. - The pipeline stays green for weeks. - A later manual scan of the deployed image shows two critical findings. - The job logs still contain the INFO line about the format disabling scanning, printed at the start of every run. Nothing in the tool failed. The run did exactly what an SBOM run does by default: it described the image. The defect is the assumption that adding an output format leaves the scanner list alone. ## Related settings that do not help - `--list-all-pkgs` decides whether a JSON report includes every package. It has no effect on which scanners run. - `--severity` and `--ignore-unfixed` filter findings. They cannot produce findings from a scanner that never ran. - Switching between `cyclonedx`, `spdx` and `spdx-json` changes the document, not the rule. ## Proving the gate works 1. Run the step against a **known-vulnerable control image** and confirm it fails. A gate that has never failed proves nothing. 2. Search the job log for `disables security scanning`; its presence means the scanner list was cleared. 3. For CycloneDX, check that the output has a `vulnerabilities` array whenever the control image is scanned. How a failing exit code maps to severities, ignore files and the rest of the CI wiring belongs to Trivy's CI integration; the point here is that the SBOM format decided nothing was scanned.

  • Does the same happen with `--format spdx-json`?
    Yes. The rule covers `cyclonedx`, `spdx` and `spdx-json`. With an explicit `--scanners vuln` the scan runs and `--exit-code` works with any of them, but only CycloneDX carries the vulnerabilities inside the document, because SPDX 2.3 has no place for them.
  • How can a change to `trivy.yaml` break this gate without anyone touching the pipeline?
    Trivy checks whether the scanners setting was set in any configuration it reads, and `scan.scanners` in `trivy.yaml` counts. While the file sets it, SBOM-format jobs keep scanning. Remove the key and every such job silently goes back to inventory-only.

saying these in an interview costs you the question

  • --exit-code fails the build on any finding, whatever the output format.
  • CycloneDX output from Trivy always lists the image's vulnerabilities.
  • Only the vulnerability scanner is switched off; secret scanning still runs.
  • Switching from cyclonedx to spdx-json brings the vulnerability scan back.
  • Trivy fails with an error when a format disables scanning.