Does a Trivy 0.74 scan fail a CI job by default when it finds a CRITICAL vulnerability, and what makes it fail?
answer
- the default exit status
- a flag that names the code
- filters run before the decision
- secrets count too, not just CVEs
basics
~10 sNo. Trivy 0.74 exits 0 whatever it finds; setting --exit-code to a non-zero value makes it exit with that code when any finding survives filtering, and --severity HIGH,CRITICAL decides which findings survive.
solid answer
~40 sBy default `--exit-code` is `0`, so a scan that reports a hundred CRITICALs still exits 0 and the job goes green. Set `--exit-code 1` (or any non-zero code) and Trivy exits with it when anything is left after filtering: a vulnerability, a FAIL misconfiguration, a secret or a license finding. `--severity HIGH,CRITICAL` narrows what is left, and it filters the report too, so the MEDIUMs vanish from the output as well as from the gate. Ignore files, `--ignore-unfixed`, an `--ignore-policy` and VEX all run before the decision. `--exit-on-eol` is a separate gate for an end-of-life base OS and is checked first. Because Trivy also exits 1 on a fatal error such as a failed database download, a distinct code like `--exit-code 2` lets the pipeline tell findings from a broken scan.
code
bash · 9 linesstatus=0
trivy image --severity HIGH,CRITICAL --exit-code 2 --exit-on-eol 3 \
registry.example.internal/shop/api:1.4.2 || status=$?
case "$status" in
0) echo "gate passed" ;;
2) echo "HIGH or CRITICAL findings remain"; exit 1 ;;
3) echo "base OS is end-of-life"; exit 1 ;;
*) echo "trivy itself failed (exit $status)"; exit 1 ;;
esacgo deeper
Remember that Trivy exits 0 by default and only fails a job when --exit-code is non-zero, and that --severity HIGH,CRITICAL limits what can fail it.
Explain the filter order behind the decision (severity, status, ignore file, Rego, VEX) and that secrets, FAIL misconfigurations and licenses count alongside vulnerabilities.
Show you keep the gate trusted: a distinct exit code so a failed database download is not mistaken for findings, --exit-on-eol as its own signal, and a separate full report because --severity trims the output.
Frame the gate as something teams must keep switched on: which finding classes fail it, how waivers are scoped, and how one checked-in trivy.yaml keeps local and CI runs identical across repositories.
## The default: Trivy reports, it does not judge A **Trivy** scan has two outputs: the report (table, JSON, SARIF and so on) and the process **exit status**. A CI system only looks at the exit status. In Trivy 0.74 the `--exit-code` flag defaults to `0`, so the scan exits 0 whether it found nothing or a hundred CRITICAL vulnerabilities; a job that runs `trivy image` with no gate flags always goes green, and the findings are information for whoever reads the log. Failing the job is an explicit opt-in, and how narrowly you opt in decides whether teams keep the gate: a gate that fires on every LOW in a base image is the one that gets commented out. ## What `--exit-code` counts Setting `--exit-code` to a non-zero value means "if any finding remains after filtering, exit with this code". Trivy evaluates the final, already-filtered result set, and the check is broader than vulnerabilities: | Finding class | Counts toward `--exit-code` when | |---|---| | Vulnerability | any remains after filtering | | Misconfiguration | its status is FAIL; passed checks never count | | Secret | any remains after filtering | | License | any remains, which needs the license scanner enabled | The default `--scanners` value is `vuln,secret`, so a plain `trivy image --exit-code 1` fails on a hard-coded access key as readily as on a CVE. That surprises teams who think of Trivy as only a CVE scanner. ## What runs before the decision The exit decision happens after the filters, which Trivy applies in this order: 1. **Severity**: `--severity` (default: all five of `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL`) keeps only the listed levels. Which severity a finding carries, and which source it came from, is the vulnerability-database topic; here it is simply the value Trivy assigned. 2. **Status**: for example `--ignore-unfixed`, which drops vulnerabilities that have no fixed version yet. 3. **Ignore file**: IDs listed in `.trivyignore`, or in a YAML ignore file named with `--ignorefile`. 4. **Rego**: an `--ignore-policy` file. 5. **VEX**: statements supplied with `--vex`. Two consequences matter in CI: - `--severity` filters the **report** as well as the gate. A run with `--severity HIGH,CRITICAL` writes a report with no MEDIUM or LOW rows at all, so a team that also wants the full picture runs a second, non-gating scan or keeps a full JSON report. - A filtered or waived finding cannot fail the build. That is the point of the filters, and it is why the waivers themselves deserve review. ## `--exit-on-eol`: a separate gate `--exit-on-eol` takes its own code and fires when Trivy detects that the scanned operating system has reached end of service or life. It is checked **before** `--exit-code`, so an end-of-life base image returns the EOL code even when vulnerabilities also remain. Giving each gate its own number lets the pipeline report which rule tripped. ## Telling findings from a broken scan When Trivy itself fails (a rate-limited database download, an image it cannot pull, a config file it cannot parse) it logs a fatal error and exits **1**. If the gate also uses `--exit-code 1`, a red job could mean "HIGH findings" or "the scanner never ran", and people learn to press re-run until it goes green. A distinct code such as `2` keeps the two apart; both still fail the job, which is the safe direction. Keeping Trivy's database in a CI cache, which the official GitHub Action does by default, cuts down those download failures; mirrors and offline database handling are a separate topic. ## Keeping the gate in one place Trivy reads `trivy.yaml` from the current directory by default, or another file named with `--config`. The gate settings are top-level keys there (`exit-code`, `exit-on-eol`, `severity`), so a developer's local run and the CI run behave the same. Precedence runs CLI flag, then `TRIVY_*` environment variable, then the config file, then the built-in default. ## Mistakes that get a gate switched off - Gating on every severity of a fresh base image, so the first run fails on dozens of LOW findings nobody can fix this sprint. - Reusing exit code 1, so a rate-limited database download looks like a vulnerability and gets re-run until green. - Forgetting that secrets are scanned by default, then discovering the gate on a test fixture that holds a fake key. - Setting the gate in a wrapper's environment and in `trivy.yaml` with different values, so local runs and CI disagree. A minimal gate on one line: ```bash trivy image --severity HIGH,CRITICAL --exit-code 2 registry.example.internal/shop/api:1.4.2 ```
- Why might a team run Trivy twice in the same CI job?Because `--severity` filters the report as well as the gate. A run with `--severity HIGH,CRITICAL --exit-code 1` writes a report with the MEDIUM and LOW findings missing. Teams that want a complete record run once with `--exit-code 0` and every severity to produce a JSON or SARIF report, then once more with the gate flags; the second run reuses the cached database.
- Where should the gate's flags live so a developer's local Trivy run matches CI?In a checked-in `trivy.yaml`, which Trivy reads from the current directory by default; `exit-code`, `exit-on-eol` and `severity` are top-level keys. A CLI flag or a `TRIVY_*` environment variable still overrides the file, so a CI wrapper that sets either one silently changes the gate. An explicit `--config` path that does not exist stops Trivy with an error.
- Does `--exit-code 1` fail a `trivy image` run that found only a hard-coded secret?Yes. The default `--scanners` value in Trivy 0.74 is `vuln,secret`, and any secret finding that survives filtering counts toward the exit code exactly like a vulnerability. Teams that only want CVEs to gate either pass `--scanners vuln` or waive the specific secret rule, but a real leaked key is usually the finding most worth failing on.
saying these in an interview costs you the question
- Trivy fails the build by default whenever it finds a CRITICAL vulnerability.
- --severity only changes the exit decision; the report still lists every severity.
- --exit-code only reacts to vulnerabilities, never to secrets or failed misconfiguration checks.
- Exit code 1 from Trivy always means vulnerabilities were found.
- A passed misconfiguration check counts toward the exit code.