Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- [Workflow failures](#workflow-failures)
- [Don't fail on results](#dont-fail-on-results)
- [Fail by severity usage example](#fail-by-severity-usage-example)
- [Fail by severity threshold usage example](#fail-by-severity-threshold-usage-example)
- [Enabling Pull Request Comment](#enabling-pull-request-comment)
- [PR Comment Example](#pr-comment-example)
- [Annotations](#annotations)
Expand Down Expand Up @@ -87,6 +88,7 @@ Ensure that you're using the <a href="https://github.com/Checkmarx/kics-github-a
| path | terraform/main.tf,Dockerfile | paths to a file or directories to scan, comma separated list | String | Yes | N/A |
| ignore_on_exit | results | defines which non-zero exit codes should be ignored (all, results, errors, none) | String | No | none |
| fail_on | high,medium | comma separated list of which severities returns exit code !=0 | String | No | high,medium,low,info |
| fail_on_threshold | high>2,low<5 | comma separated list of severity[operator]value pairs. Supported operators: >, <, >=, <=. Example: 'high>2,low<5' means fail if high > 2 or low < 5. If set, fail_on_threshold takes precedence over fail_on and ignore_on_exit. | Map | No | N/A |
| timeout | 75 | number of seconds the query has to execute before being canceled | String | No | 60 |
| profiling | CPU | turns on profiler that prints resource consumption in the logs during the execution (CPU, MEM) | String | No | N/A |
| config_path | ./kics.config | path to configuration file | String | No | N/A |
Expand Down Expand Up @@ -172,6 +174,34 @@ If want your pipeline just to fail on HIGH and MEDIUM severity results and KICS
cat myResults/results.json
```

### Fail by severity threshold usage example

If you want your pipeline to fail only if the number of HIGH severity issues is greater than 2, or LOW severity issues is less than 5:

```yaml
steps:
- uses: actions/checkout@v3
- name: run kics Scan
uses: checkmarx/kics-github-action@v2.1.11
with:
path: 'terraform,my-other-sub-folder/Dockerfile'
fail_on_threshold: 'high>2,low<5'
output_path: myResults/
- name: display kics results
run: |
cat myResults/results.json
```

**Note:** If `fail_on_threshold` is set, it takes precedence over `fail_on` and `ignore_on_exit`. The workflow will only fail if a threshold is exceeded, regardless of other settings.

Supported operators:
- `>`: greater than
- `<`: less than
- `>=`: greater than or equal to
- `<=`: less than or equal to

If `fail_on_threshold` is not provided, the action will use the default exit code logic and behave as before.

## Enabling Pull Request Comment

`GITHUB_TOKEN` enables this github action to access github API and post comments in a pull request:
Expand Down
4 changes: 4 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ inputs:
fail_on:
description: "comma separated list of which severities returns exit code !=0"
required: false
fail_on_threshold:
description: "Comma separated list of severity[operator]value pairs. Supported operators: >, <, >=, <=. Example: high>2,low<5 means fail if high > 2 or low < 5."
required: false
default: ""
timeout:
description: "number of seconds the query has to execute before being canceled"
required: false
Expand Down
61 changes: 58 additions & 3 deletions src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,67 @@ function processOutputPath(output) {
}
}

function setWorkflowStatus(statusCode) {
function setWorkflowStatus(statusCode, results, failOnThreshold) {
console.log(`KICS scan status code: ${statusCode}`);

// If failOnThreshold is provided, check severity counters
if (failOnThreshold && typeof results === 'object' && results.severity_counters) {
let failed = false;
for (const [severity, rule] of Object.entries(failOnThreshold)) {
const count = results.severity_counters[severity.toUpperCase()] || 0;
if (rule && rule.op && typeof rule.value === 'number') {
if ((rule.op === 'gt' && count > rule.value) ||
(rule.op === 'lt' && count < rule.value) ||
(rule.op === 'gte' && count >= rule.value) ||
(rule.op === 'lte' && count <= rule.value)) {
console.log(`Failing workflow: ${severity} issues (${count}) ${rule.op} ${rule.value}`);
failed = true;
}
}
}
if (failed) {
core.setFailed(`KICS scan failed due to fail_on_threshold: ${JSON.stringify(failOnThreshold)}`);
return;
} else {
// If fail_on_threshold is set and not triggered, always pass
return;
}
}

// Default behavior
if (statusCode === "0") {
return;
}

core.setFailed(`KICS scan failed with exit code ${statusCode}`);
}

function parseFailOnThreshold(input) {
if (!input) return undefined;
// Accept JSON or comma-separated string (e.g., "high>5,low<20")
try {
if (input.trim().startsWith('{')) {
return JSON.parse(input);
}
const map = {};
input.split(',').forEach(pair => {
const match = pair.trim().match(/^(\w+)\s*([><]=?)\s*(\d+)$/);
if (match) {
const [, key, op, value] = match;
let opName = '';
if (op === '>') opName = 'gt';
else if (op === '>=') opName = 'gte';
else if (op === '<') opName = 'lt';
else if (op === '<=') opName = 'lte';
map[key.toLowerCase()] = { op: opName, value: Number(value) };
}
});
return Object.keys(map).length ? map : undefined;
} catch (e) {
console.error('Could not parse INPUT_FAIL_ON_THRESHOLD:', e);
return undefined;
}
}

async function main() {
console.log("Running KICS action...");

Expand All @@ -56,6 +107,10 @@ async function main() {
const outputFormats = process.env.INPUT_OUTPUT_FORMATS;
const exitCode = process.env.KICS_EXIT_CODE

// Read fail_on_threshold using core.getInput for consistency
const failOnThresholdRaw = core.getInput('fail_on_threshold');
const failOnThreshold = parseFailOnThreshold(failOnThresholdRaw);

try {
const octokit = github.getOctokit(githubToken);
let context = {};
Expand Down Expand Up @@ -87,7 +142,7 @@ async function main() {
await commenter.postJobSummary(parsedResults, commentsWithQueries.toLocaleLowerCase() === "true", excludedColumnsForCommentsWithQueries);
}

setWorkflowStatus(exitCode);
setWorkflowStatus(exitCode, parsedResults, failOnThreshold);
cleanupOutput(outputPath.resultsJSONFile, outputFormats);
} catch (e) {
console.error(e);
Expand Down