Skip to content

Commit

Permalink
Add table option for output-format vulnerabilities in console (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
ken-chou-glia committed Sep 19, 2022
1 parent 961fe4c commit d5aa5b6
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 42 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ The inputs `image`, `path`, and `sbom` are mutually exclusive to specify the sou
| `sbom` | The SBOM to scan | N/A |
| `registry-username` | The registry username to use when authenticating to an external registry | |
| `registry-password` | The registry password to use when authenticating to an external registry | |
| `fail-build` | Fail the build if a vulnerability is found with a higher severity. That severity defaults to `"medium"` and can be set with `severity-cutoff`. | `true` |
| `output-format` | Set the output parameter after successful action execution. Valid choices are "json" and "sarif" | `sarif` |
| `severity-cutoff` | Optionally specify the minimum vulnerability severity to trigger a failure. Valid choices are "negligible", "low", "medium", "high" and "critical". Any vulnerability with a severity less than this value will lead to a "warning" result. Default is "medium". | `"medium"` |
| `fail-build` | Fail the build if a vulnerability is found with a higher severity. That severity defaults to `medium` and can be set with `severity-cutoff`. | `true` |
| `output-format` | Set the output parameter after successful action execution. Valid choices are `json`, `sarif`, and `table`, where `table` output will print to the console instead of generating a file. | `sarif` |
| `severity-cutoff` | Optionally specify the minimum vulnerability severity to trigger a failure. Valid choices are "negligible", "low", "medium", "high" and "critical". Any vulnerability with a severity less than this value will lead to a "warning" result. Default is "medium". | `medium` |

### Action Outputs

Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ inputs:
required: false
default: "true"
output-format:
description: 'Set the output parameter after successful action execution. Valid choices are "json" and "sarif".'
description: 'Set the output parameter after successful action execution. Valid choices are "json", "sarif", and "table".'
required: false
default: "sarif"
severity-cutoff:
Expand Down
43 changes: 24 additions & 19 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ async function runScan({ source, failBuild, severityCutoff, outputFormat }) {
}

const SEVERITY_LIST = ["negligible", "low", "medium", "high", "critical"];
const FORMAT_LIST = ["sarif", "json"];
const FORMAT_LIST = ["sarif", "json", "table"];
let cmdArgs = [];

if (core.isDebug()) {
Expand Down Expand Up @@ -227,34 +227,39 @@ async function runScan({ source, failBuild, severityCutoff, outputFormat }) {
core.debug(cmdOutput);
}

if (outputFormat === "sarif") {
const SARIF_FILE = "./results.sarif";
fs.writeFileSync(SARIF_FILE, cmdOutput);
out.sarif = SARIF_FILE;
} else {
const REPORT_FILE = "./results.json";
fs.writeFileSync(REPORT_FILE, cmdOutput);
out.report = REPORT_FILE;
}

if (failBuild === true && exitCode > 0) {
core.setFailed(
`Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher`
);
switch (outputFormat) {
case "sarif": {
const SARIF_FILE = "./results.sarif";
fs.writeFileSync(SARIF_FILE, cmdOutput);
out.sarif = SARIF_FILE;
break;
}
case "json": {
const REPORT_FILE = "./results.json";
fs.writeFileSync(REPORT_FILE, cmdOutput);
out.report = REPORT_FILE;
break;
}
default: // e.g. table
core.info(cmdOutput);
}

// If there is a non-zero exit status code there are a couple of potential reporting paths
if (failBuild === false && exitCode > 0) {
// There was a non-zero exit status but it wasn't because of failing severity, this must be
// a grype problem
if (exitCode > 0) {
if (!severityCutoff) {
// There was a non-zero exit status but it wasn't because of failing severity, this must be
// a grype problem
core.warning("grype had a non-zero exit status when running");
} else if (failBuild === true) {
core.setFailed(
`Failed minimum severity level. Found vulnerabilities with level '${severityCutoff}' or higher`
);
} else {
// There is a non-zero exit status code with severity cut off, although there is still a chance this is grype
// that is broken, it will most probably be a failed severity. Using warning here will make it bubble up in the
// Actions UI
core.warning(
`Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher`
`Failed minimum severity level. Found vulnerabilities with level '${severityCutoff}' or higher`
);
}
}
Expand Down
43 changes: 24 additions & 19 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ async function runScan({ source, failBuild, severityCutoff, outputFormat }) {
}

const SEVERITY_LIST = ["negligible", "low", "medium", "high", "critical"];
const FORMAT_LIST = ["sarif", "json"];
const FORMAT_LIST = ["sarif", "json", "table"];
let cmdArgs = [];

if (core.isDebug()) {
Expand Down Expand Up @@ -212,34 +212,39 @@ async function runScan({ source, failBuild, severityCutoff, outputFormat }) {
core.debug(cmdOutput);
}

if (outputFormat === "sarif") {
const SARIF_FILE = "./results.sarif";
fs.writeFileSync(SARIF_FILE, cmdOutput);
out.sarif = SARIF_FILE;
} else {
const REPORT_FILE = "./results.json";
fs.writeFileSync(REPORT_FILE, cmdOutput);
out.report = REPORT_FILE;
}

if (failBuild === true && exitCode > 0) {
core.setFailed(
`Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher`
);
switch (outputFormat) {
case "sarif": {
const SARIF_FILE = "./results.sarif";
fs.writeFileSync(SARIF_FILE, cmdOutput);
out.sarif = SARIF_FILE;
break;
}
case "json": {
const REPORT_FILE = "./results.json";
fs.writeFileSync(REPORT_FILE, cmdOutput);
out.report = REPORT_FILE;
break;
}
default: // e.g. table
core.info(cmdOutput);
}

// If there is a non-zero exit status code there are a couple of potential reporting paths
if (failBuild === false && exitCode > 0) {
// There was a non-zero exit status but it wasn't because of failing severity, this must be
// a grype problem
if (exitCode > 0) {
if (!severityCutoff) {
// There was a non-zero exit status but it wasn't because of failing severity, this must be
// a grype problem
core.warning("grype had a non-zero exit status when running");
} else if (failBuild === true) {
core.setFailed(
`Failed minimum severity level. Found vulnerabilities with level '${severityCutoff}' or higher`
);
} else {
// There is a non-zero exit status code with severity cut off, although there is still a chance this is grype
// that is broken, it will most probably be a failed severity. Using warning here will make it bubble up in the
// Actions UI
core.warning(
`Failed minimum severity level. Found vulnerabilities with level ${severityCutoff} or higher`
`Failed minimum severity level. Found vulnerabilities with level '${severityCutoff}' or higher`
);
}
}
Expand Down
43 changes: 43 additions & 0 deletions tests/action_args.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,4 +73,47 @@ describe("Github action args", () => {
spyInput.mockRestore();
spyOutput.mockRestore();
});

it("runs with table output", async () => {
const inputs = {
image: "localhost:5000/match-coverage/debian:latest",
"fail-build": "true",
"output-format": "table",
"severity-cutoff": "medium",
};
const spyInput = jest.spyOn(core, "getInput").mockImplementation((name) => {
try {
return inputs[name];
} finally {
inputs[name] = true;
}
});

const outputs = {};
const spyOutput = jest
.spyOn(core, "setOutput")
.mockImplementation((name, value) => {
outputs[name] = value;
});

let stdout = "";
const spyStdout = jest.spyOn(core, "info").mockImplementation((value) => {
stdout += value;
});

await run();

Object.keys(inputs).map((name) => {
expect(inputs[name]).toBe(true);
});

expect(stdout).toContain("VULNERABILITY");

expect(outputs["sarif"]).toBeFalsy();
expect(outputs["json"]).toBeFalsy();

spyInput.mockRestore();
spyOutput.mockRestore();
spyStdout.mockRestore();
});
});

0 comments on commit d5aa5b6

Please sign in to comment.