Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for cyclonedx and cyclonedx-json output-formats #396

Merged
merged 7 commits into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -122,3 +122,5 @@ typings/
results.sarif
vulnerabilities.json
results.json
results.bom
results.bom.json
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ The inputs `image`, `path`, and `sbom` are mutually exclusive to specify the sou
| `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`, `sarif`, and `table`, where `table` output will print to the console instead of generating a file. | `sarif` |
| `output-format` | Set the output parameter after successful action execution. Valid choices are `json`, `sarif`, `cyclonedx`, `cyclonedx-json` 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` |
| `only-fixed` | Specify whether to only report vulnerabilities that have a fix available. | `false` |
| `add-cpes-if-none` | Specify whether to autogenerate missing CPEs. | `false` |
Expand All @@ -142,6 +142,8 @@ The inputs `image`, `path`, and `sbom` are mutually exclusive to specify the sou
| ----------- | ------------------------------------------------------------ | ------ |
| `sarif` | Path to the SARIF report file, if `output-format` is `sarif` | string |
| `json` | Path to the report file , if `output-format` is `json` | string |
| `cyclonedx` | Path to the CycloneDX report file, if `output-format` is `cyclonedx` | string |
| `cyclonedx-json` | Path to the CycloneDX JSON report file, if `output-format` is `cyclonedx-json` | string |

### Example Workflows

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", "sarif", and "table".'
description: 'Set the output parameter after successful action execution. Valid choices are "json", "sarif", "cyclonedx", "cyclonedx-json" and "table".'
required: false
default: "sarif"
severity-cutoff:
Expand Down
14 changes: 13 additions & 1 deletion dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ async function runScan({
}

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

if (core.isDebug()) {
Expand Down Expand Up @@ -418,6 +418,18 @@ async function runScan({
out.json = REPORT_FILE;
break;
}
case "cyclonedx": {
const CYCLONEDX_FILE = "./results.bom";
fs.writeFileSync(CYCLONEDX_FILE, stdout);
out.cyclonedx = CYCLONEDX_FILE;
break;
}
case "cyclonedx-json": {
const CYCLONEDX_JSON_FILE = "./results.bom.json";
fs.writeFileSync(CYCLONEDX_JSON_FILE, stdout);
out["cyclonedx-json"] = CYCLONEDX_JSON_FILE;
break;
}
default: // e.g. table
core.info(stdout);
}
Expand Down
14 changes: 13 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ async function runScan({
}

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

if (core.isDebug()) {
Expand Down Expand Up @@ -404,6 +404,18 @@ async function runScan({
out.json = REPORT_FILE;
break;
}
case "cyclonedx": {
const CYCLONEDX_FILE = "./results.bom";
fs.writeFileSync(CYCLONEDX_FILE, stdout);
out.cyclonedx = CYCLONEDX_FILE;
break;
}
case "cyclonedx-json": {
const CYCLONEDX_JSON_FILE = "./results.bom.json";
fs.writeFileSync(CYCLONEDX_JSON_FILE, stdout);
out["cyclonedx-json"] = CYCLONEDX_JSON_FILE;
break;
}
default: // e.g. table
core.info(stdout);
}
Expand Down
30 changes: 30 additions & 0 deletions tests/action.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,36 @@ describe("Github action", () => {
expect(outputs["json"]).toBeFalsy();
});

it("runs with cyclonedx output", async () => {
const outputs = mockIO({
image: "",
path: "tests/fixtures/npm-project",
"fail-build": "true",
"output-format": "cyclonedx",
"severity-cutoff": "medium",
"add-cpes-if-none": "true",
});

await run();

expect(outputs["cyclonedx"]).toBe("./results.bom");
});

it("runs with cyclonedx-json output", async () => {
const outputs = mockIO({
image: "",
path: "tests/fixtures/npm-project",
"fail-build": "true",
"output-format": "cyclonedx-json",
"severity-cutoff": "medium",
"add-cpes-if-none": "true",
});

await run();

expect(outputs["cyclonedx-json"]).toBe("./results.bom.json");
});

it("runs with environment variables", async () => {
mockIO({
path: "tests/fixtures/npm-project",
Expand Down
34 changes: 34 additions & 0 deletions tests/grype_command.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,40 @@ describe("Grype command args", () => {
expect(args).toEqual(["-o", "sarif", "--fail-on", "high", "dir:."]);
});

it("is invoked with cyclonedx output", async () => {
const args = await mockRun({
source: "dir:.",
"fail-build": "false",
"output-format": "cyclonedx",
"severity-cutoff": "high",
version: "0.6.0",
"only-fixed": "false",
"add-cpes-if-none": "false",
"by-cve": "false",
});
expect(args).toEqual(["-o", "cyclonedx", "--fail-on", "high", "dir:."]);
});

it("is invoked with cyclonedx-json output", async () => {
const args = await mockRun({
source: "dir:.",
"fail-build": "false",
"output-format": "cyclonedx-json",
"severity-cutoff": "high",
version: "0.6.0",
"only-fixed": "false",
"add-cpes-if-none": "false",
"by-cve": "false",
});
expect(args).toEqual([
"-o",
"cyclonedx-json",
"--fail-on",
"high",
"dir:.",
]);
});

it("is invoked with values", async () => {
const args = await mockRun({
image: "asdf",
Expand Down
Loading