A trivy-action step sets severity HIGH,CRITICAL, exit-code 1 and format sarif, yet fails the build on MEDIUM findings — why, and how do you fix it?
answer
- the action rewrites its own inputs
- inputs become TRIVY_ variables
- SARIF wants every severity
- an opt-in input, or two steps
basics
~20 strivy-action unsets the severity input for SARIF output unless limit-severities-for-sarif is true, so without a config-file severity Trivy reports and gates on every level. Set that input to true, or split reporting and gating into two steps.
solid answer
~40 sThe action turns its inputs into `TRIVY_*` environment variables, then its entrypoint checks the format: when it is `sarif` and `limit-severities-for-sarif` is not `true`, it runs `unset TRIVY_SEVERITY` so the SARIF holds every severity. With the severity gone, Trivy falls back to the config file's `severity` if one is loaded, otherwise to all five, and `--exit-code 1` now fires on MEDIUM and LOW. Two fixes: set `limit-severities-for-sarif: 'true'`, which keeps the filter for the SARIF and the gate alike, or use two steps, one producing the full SARIF with `exit-code: 0`, then a gate with `format: table`, the severity, `exit-code: 1` and `skip-setup-trivy: true`. Precedence underneath is Trivy's own: CLI flag, then environment variable, then `trivy.yaml`.
code
yaml · 15 lines- name: Full SARIF report (never fails)
uses: aquasecurity/trivy-action@<full-commit-sha>
with:
image-ref: registry.example.internal/shop/api:${{ github.sha }}
format: sarif
output: trivy-results.sarif
exit-code: '0'
- name: Gate on HIGH and CRITICAL
uses: aquasecurity/trivy-action@<full-commit-sha>
with:
image-ref: registry.example.internal/shop/api:${{ github.sha }}
format: table
severity: HIGH,CRITICAL
exit-code: '1'
skip-setup-trivy: truego deeper
Recall that trivy-action inputs such as severity, exit-code and format become Trivy settings, and that format sarif is treated specially.
Explain the mechanism: inputs become TRIVY_ environment variables, the entrypoint unsets TRIVY_SEVERITY for SARIF, and Trivy then falls back to the config file or all severities.
Show the fix you would ship: limit-severities-for-sarif, or a full-report step with exit-code 0 plus a separate gate step, and how you read the step log to prove which layer set a value.
Decide whether teams should drive Trivy through action inputs or a checked-in trivy.yaml, given the precedence rules and the action's special cases.
## How the action passes settings to Trivy The official **trivy-action** for GitHub Actions is a composite action. It installs Trivy (through `setup-trivy`, unless `skip-setup-trivy` is true), restores a cache, writes the inputs that map to Trivy settings as exported `TRIVY_*` variables into a small env file, and runs an `entrypoint.sh` that sources that file and calls `trivy <scan-type> <scan-ref>`. So `severity: HIGH,CRITICAL` reaches Trivy as `TRIVY_SEVERITY`, `exit-code: 1` as `TRIVY_EXIT_CODE`, `format: sarif` as `TRIVY_FORMAT`. Two details of that mapping matter: - An input left equal to its default (for example `severity` at `UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL`) is **not exported**, because GitHub Actions cannot tell "not set" from "set to the default". A value from `trivy.yaml` then wins for that setting. - Trivy's own precedence decides the rest: **CLI flag**, then **environment variable**, then **config file**, then the built-in default. Action inputs arrive as environment variables, so they beat `trivy.yaml`; the action puts no other flags on the command line, so in practice they are the top layer. ## The SARIF special case The entrypoint contains one deliberate exception. When `TRIVY_FORMAT` is `sarif` and the input `limit-severities-for-sarif` is not `true`, it prints "Building SARIF report with all severities" and runs `unset TRIVY_SEVERITY`. The intent is that a code-scanning dashboard should receive every result. The side effect is on the gate: 1. The severity filter is gone, so Trivy uses `trivy.yaml`'s `severity` if a config file is loaded, else all five levels. 2. Every surviving finding, MEDIUM and LOW included, counts toward `TRIVY_EXIT_CODE`. 3. The step fails on findings the author believed were filtered out, and the log line above is the main hint. This is a property of the action's script, not of Trivy: the same flags on the Trivy command line would keep the severity filter for SARIF. ## Fixes | Fix | SARIF contents | Gate behaviour | |---|---|---| | `limit-severities-for-sarif: 'true'` | only HIGH and CRITICAL | fails on HIGH and CRITICAL | | Two steps: SARIF with `exit-code: 0`, then a gate step | every severity | gate step fails on HIGH and CRITICAL | | Severity moved into `trivy.yaml` via `trivy-config` | config's severities | follows the config file | The two-step form is the pattern the action's README shows for multiple calls: the second call sets `skip-setup-trivy: true` because Trivy is already installed, and it reuses the database already in the cache directory. The third row works because only the environment variable is unset; a severity coming from the config file is untouched, though it then trims the SARIF as well. ## Why the action behaves this way The behaviour is deliberate. A code-scanning dashboard is a triage surface: teams want to see every result there, mark false positives and track the backlog, while the build gate should stay narrow. One action step cannot serve both purposes with one severity setting, so the action chose to favour the dashboard by default and made the gate the author's problem. Knowing that, the two-step form is not a workaround but the intended design: one call reports, one call decides. To prove which layer set a value in a failing run: - Look for the line "Building SARIF report with all severities" in the step log. - Check whether a `trivy.yaml` sits in the working directory or is named with `trivy-config`. - Compare each input with its documented default; a value equal to the default was never passed to Trivy. ## The cache the action keeps With `cache` at its default of `true`, the action restores and saves Trivy's cache directory, by default `${{ github.workspace }}/.cache/trivy`, through `actions/cache` with a key that includes the current date, and its README recommends keeping it on to avoid rate limiting. Caches are scoped by branch, so the README suggests a scheduled job on the default branch to keep a fresh database there for other branches to restore. ## Diagnosing this class of problem - Read the step log first: the action prints which ignore files it used and the SARIF severity message. - Remember that `trivy_envs.txt` is rewritten per step, so settings from one action call no longer leak into the next. - When a value seems ignored, ask which layer set it: input (environment), config file or default.
- A trivy-action step passes `severity: UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL` and the repository's `trivy.yaml` says `severity: [CRITICAL]`. What does Trivy use?CRITICAL only. The action does not export an input whose value equals its default, because it cannot distinguish "unset" from "default", so no `TRIVY_SEVERITY` reaches Trivy and the config file's value applies. Writing the default explicitly does not override the config file.
- Why does the second trivy-action call in a job set `skip-setup-trivy: true`?The first call already installed Trivy through `setup-trivy`; skipping it saves time and avoids fetching the installer again. The second call still restores the same cache directory, so the vulnerability database downloaded by the first call is reused rather than downloaded twice.
saying these in an interview costs you the question
- trivy-action passes the severity input through unchanged for every output format.
- The SARIF severity behaviour is Trivy's own and happens on the command line too.
- A value in trivy.yaml overrides a TRIVY_ environment variable.
- Setting an action input to its default value overrides the config file.
- Turning the action's cache off has no effect apart from slightly slower runs.