skip to content

A KICS 2.2 scan in CI exits with status 40 — what does that mean, and how do `--fail-on` and `--ignore-on-exit` change it?

level: middleimportance: must knowfreq 19%

answer

  1. severity-coded exit status
  2. the worst counted severity wins
  3. a list, not a threshold
  4. results versus engine errors
  5. 60 arrived with KICS 2.0

basics

~20 s

Status 40 means the worst counted finding was MEDIUM, with no counted CRITICAL (60) or HIGH (50). --fail-on lists which severities may set the status, and --ignore-on-exit can mute result codes, engine-error codes, or both.

solid answer

~40 s

KICS sets its exit status from the most severe result among the severities `--fail-on` counts: 60 for CRITICAL, 50 HIGH, 40 MEDIUM, 30 LOW, 20 INFO, and 0 when nothing counted was found; 126 is an engine error, 130 a signal interrupt and 70 a failed `remediate`. So 40 means at least one MEDIUM finding and no counted CRITICAL or HIGH. `--fail-on` (default `critical,high,medium,low,info`) is a list, not a threshold: `--fail-on high` on its own lets a run whose only findings are CRITICAL exit 0. `--ignore-on-exit` takes `none` (the default), `results`, `errors` or `all`; with `results`, only engine errors fail the job. Either way the report is written in full before KICS exits.

code

bash · 9 lines
bash
set +e
kics scan -p . -o ./kics-out --report-formats json,sarif --fail-on critical,high
status=$?
set -e
case "$status" in
  0) echo "no critical or high findings" ;;
  50|60) echo "blocking findings, exit $status"; exit 1 ;;
  *) echo "KICS did not finish cleanly, exit $status"; exit "$status" ;;
esac

go deeper

for a junior

Remember that KICS's exit status encodes the worst finding: 60 critical, 50 high, 40 medium, 30 low, 20 info, and 0 when nothing counted was found.

for a middle

Explain how --fail-on selects which severities count, why it is a list rather than a threshold, and what each --ignore-on-exit value hides.

for a senior

Show a gate that blocks on exactly the codes you mean, still fails on engine errors, and survived the KICS 2.0 change that introduced status 60.

for a principal

Discuss whether the scanner's exit status or a separate step reading the report should own the merge decision, given that tool upgrades can change the codes.

## The exit-status table KICS documents two groups of exit statuses. The first encodes the **worst result found**; the second reports that something went wrong in the tool itself. | Status | Meaning | |---|---| | `0` | no counted results | | `60` | at least one `CRITICAL` result | | `50` | at least one `HIGH` result | | `40` | at least one `MEDIUM` result | | `30` | at least one `LOW` result | | `20` | at least one `INFO` result | | `70` | remediation error (the `remediate` command) | | `126` | engine error | | `130` | signal interrupt | ## How KICS picks the status After the scan, KICS has already printed the results and written every requested report. It then computes the status: 1. Walk the severities in order: `CRITICAL`, `HIGH`, `MEDIUM`, `LOW`, `INFO`. 2. Skip any severity that is **not in the `--fail-on` list**. 3. Return the code of the first remaining severity with a count above zero. 4. If none qualifies, return 0. `TRACE` results — the bill-of-materials entries `-m, --bom` adds — never set a status. So status 40 says: at least one `MEDIUM` result, and no `CRITICAL` or `HIGH` result among the counted severities. It says nothing about how many findings there were; the counts live in `severity_counters` in the JSON report. ## `--fail-on` is a list, not a threshold `--fail-on` accepts any of `critical`, `high`, `medium`, `low` and `info`, and defaults to all five. An unknown value stops KICS with an error. Because step 2 skips unlisted severities, the flag selects severities rather than drawing a line: | `--fail-on` | Findings in the scan | Exit status | |---|---|---| | default (all five) | 2 MEDIUM, 5 LOW | 40 | | `critical,high` | 2 MEDIUM, 5 LOW | 0 | | `critical,high` | 1 CRITICAL, 3 HIGH | 60 | | `high` | 1 CRITICAL only | 0 | | `low` | 1 HIGH, 2 LOW | 30 | The fourth row is the classic surprise: someone writes `--fail-on high` meaning "high or worse", and a `CRITICAL` finding passes the gate. Write every severity you mean, for example `--fail-on critical,high`. `--fail-on` changes only the status. Every finding still appears in the console and in every report. That is the difference from `--exclude-severities`, which removes queries of those severities before evaluation so their findings never exist. ## `--ignore-on-exit` separates findings from failures | Value | Result codes (20-60) | Error codes | |---|---|---| | `none` (default) | returned | returned | | `results` | suppressed, exit 0 | returned | | `errors` | returned | suppressed | | `all` | suppressed | suppressed | `results` is the setting for a **report-only** stage: findings are published, yet a broken run still turns the job red. `all` makes a crashed or interrupted scan look exactly like a clean one, which is rarely what anybody wants. ## KICS 2.0 and the CRITICAL code KICS 2.0 added the `CRITICAL` severity with its own exit status, 60, and listed that as a **breaking change**. Some queries were re-rated at the same time; their `metadata.json` keeps the earlier rating in `oldSeverity`, and `--old-severities` reports those older ratings, which never include `CRITICAL`. A script written for KICS 1.x that blocks only when the status equals 50 lets a 60 straight through after the upgrade. ## Wiring it into a CI job - Capture the status explicitly rather than relying on the shell to stop at the first non-zero exit. - Test the codes you mean, and treat any unexpected status as a broken run, not as a pass. - Avoid `|| true` after the command: it hides 126 and 130 along with the findings. - Keep the JSON report as an artefact, so the reason for a red job is one click away. ## The status is a summary, the report is the evidence The exit status compresses a whole scan into one number, and it loses information on purpose: it cannot say how many findings there were, which files they sit in, or whether a query failed to run. When a decision needs more than "worst severity found" — say, blocking only when the HIGH count rises — the step reads `severity_counters` and `total_counter` from the JSON report instead of the status. The report is written before KICS exits, whatever the status turns out to be, so that step always has its input.

  • How is `--exclude-severities` different from leaving a severity off `--fail-on`?
    `--fail-on` only decides which severities may set the exit status; every finding still appears in the report. `--exclude-severities` removes queries of those severities before evaluation, so their findings never exist in any report or counter. Use `--fail-on` to stop blocking on LOW while keeping it visible, and `--exclude-severities` only when nobody should see those results at all.
  • Why is `--ignore-on-exit all` a poor way to make a KICS stage non-blocking?
    `all` suppresses error codes as well as result codes, so an engine error (126) or an interrupted run (130) also ends with status 0, and a partial scan looks like a clean one. `results` gives the same non-blocking behaviour for findings while still failing the job when the tool itself breaks. Pair it with a check on `queries_failed_to_execute` in the JSON report.
  • Where does a KICS result's severity come from?
    From the `severity` field in the matching query's `metadata.json`; KICS does not rescore it for your environment. Queries re-rated in KICS 2.x keep their earlier rating in `oldSeverity`, and `--old-severities` reports those older values, which never include CRITICAL.

--fail-on works like a guest list at a door rather than a height bar: only the severities named on the list can set off the alarm, so leaving CRITICAL off the list lets a critical finding walk straight through.

saying these in an interview costs you the question

  • The KICS exit status is the number of findings.
  • --fail-on high means fail on HIGH or anything worse.
  • --ignore-on-exit all is the safe way to make the scan non-blocking.
  • A non-zero KICS exit means the report was never written.
  • Status 50 is still the most severe result code KICS can return.
  • Leaving LOW off --fail-on removes LOW findings from the report.