Safeguard
Developer Tools•

Fail a CI build on critical findings with the Safeguard CLI and jq

The scan command reports but does not fail the build. A short jq step on the output file adds a severity gate you control, with a note on exit code 2.

Safeguard Security Team
4 min read

The Safeguard CLI scan command reports findings but does not fail a build by itself, and the documentation says there is no severity threshold flag. You can add a gate in two lines: run the scan, then use jq on the vulnerability output file to exit non-zero when a critical match is present.

The pattern

The CLI documentation shows this GitHub Actions shape, with the scan writing into an output folder and jq reading the grype style file:

- name: Scan the working tree
  run: safeguard scan --dir . --output out/

- name: Gate the build
  run: |
    jq -e '[.data.matches[].vulnerability.severity | ascii_downcase] | index("critical") | not' out/*/vuln.json

The jq command with the exit flag returns non-zero when the expression is false or null. Here the expression is true when no critical entry exists, so the step passes only when nothing critical is found.

The glob in the path implies a subfolder per scan under the output directory. Check your own output tree once so the glob matches exactly one file.

Handle the incomplete scan case

Before the jq step, make sure the scan itself completed. Exit code 2 means results were written but the vulnerability step did not run. In a plain shell step that is easy to miss, because GitHub Actions only fails on non-zero codes, and code 2 is non-zero, so the scan step would already fail. If you wrap the command in a script that swallows codes, add a check. See the exit codes post for the pattern.

Choosing the threshold

Blocking on critical only is a common starting point. A few notes before you pick:

  • Start with critical, watch the failure rate for a week, then consider adding high.
  • Decide up front what happens to a known, accepted finding. Without an exception path, teams end up deleting the gate.
  • Remember that severity is a score, not exploitability. A critical in a function you never call still blocks the build under this check.

If you want severity combined with context such as reachability, that is what platform policies and gates are for. The CLI documentation says gates are defined and evaluated in the platform, not in the CLI.

Counting instead of failing

You may prefer a report first. This prints the number of matches per severity so you can watch trends before enforcing anything:

jq -r '.data.matches[].vulnerability.severity' out/*/vuln.json | sort | uniq -c

Run it in warn-only mode for a sprint, share the numbers, then switch to the failing gate.

Keep the artifacts

Upload the output folder as a build artifact whatever the result, so a developer can open the file that caused the failure. A gate that blocks a merge with no evidence attached creates support tickets.

You can see a pull request gate in action in the interactive demo.

Click through it at your own pace, or open it full screen.

Quick check

Does the CLI have a fail on severity flag for scan?

The scan documentation says no. Use a post-scan check such as jq, or enforce with platform policies and gates.

Why did my jq step say no such file?

The scan writes into a subfolder under the output directory. List the folder and adjust the path or glob.

Safeguard also provides a free scan at gold.safeguard.sh/scan for a quick comparison against your pipeline results.

Sources

Last reviewed: 19 October 2026.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.