Skip to content

Commit e1d42da

Browse files
committed
[ET-394] Add package type and risk accepted filters
1 parent 4398cde commit e1d42da

11 files changed

+372
-75
lines changed

README.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
# Sysdig Secure Inline Scan Action
32

43
> 🚧 **Warning**: To use the Legacy Scanning Engine Action, please use version v3.* and visit the [previous README](./README.v3.md).
@@ -17,6 +16,9 @@ This action performs analysis on a specific container image and posts the result
1716
| `stop-on-failed-policy-eval` | Fail the job if the Policy Evaluation is Failed. | |
1817
| `stop-on-processing-error` | Fail the job if the Scanner terminates execution with errors. | |
1918
| `severity-at-least` | Filtering option to only report vulnerabilities with at least the specified severity. Can take `critical`, `high`, `medium`, `low`, `negligible` or `any`. Default value "any" for no filtering. For example, if `severity-at-least` is set to `medium`, only Medium, High or Critical vulnerabilities will be reported. | `any` |
19+
| `package-types` | Comma-separated list of package types to include in the report (e.g. `java,javascript`). Only vulnerabilities found in these types of packages will be included. If empty, no inclusion filter is applied. | |
20+
| `not-package-types` | Comma-separated list of package types to exclude from the report (e.g. `os`). Vulnerabilities found in these types of packages will be excluded. If empty, no exclusion filter is applied. | |
21+
| `exclude-accepted` | Set to `true` to exclude vulnerabilities that have accepted risks (`acceptedRisks`). Useful to focus only on unresolved findings. | |
2022
| `group-by-package` | Enable grouping the vulnerabilities in the SARIF report by package. Useful if you want to manage security per package or condense the number of findings. | |
2123
| `standalone` | Enable standalone mode. Do not depend on Sysdig backend for execution, avoiding the need of specifying 'sysdig-secure-token' and 'sysdig-secure-url'. Recommended when using runners with no access to the internet. May require to specify custom `cli-scanner-url` and `db-path`. | |
2224
| `db-path` | Specify the directory for the vulnerabilities database to use while scanning. Useful when running in standalone mode. | |
@@ -33,6 +35,23 @@ This action performs analysis on a specific container image and posts the result
3335
| `minimum-severity` | Minimum severity to fail when scanning in IaC mode. | |
3436
| `iac-scan-path` | Path to the IaC files to scan. | |
3537

38+
### Filtering Examples
39+
40+
- **severity-at-least:**
41+
`medium` → Only Medium, High, and Critical findings will be reported.
42+
43+
- **package-types:**
44+
`java,javascript` → Only vulnerabilities in Java or JavaScript packages will be included.
45+
46+
- **not-package-types:**
47+
`os` → Excludes vulnerabilities found in OS packages.
48+
49+
- **exclude-accepted:**
50+
`true` → Vulnerabilities that are marked as "accepted" (i.e., with risk acceptances) are excluded from the report.
51+
52+
> ℹ️ You can combine these filters to focus the report on just what you care about!
53+
54+
---
3655

3756
## SARIF Report
3857

action.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ inputs:
4747
description: Filtering option to only report vulnerabilities with at least the specified severity. Can take [critical|high|medium|low|negligible|any]. Default value "any" for no filtering.
4848
default: any
4949
required: false
50+
package-types:
51+
description: Comma-separated list of package types to include in the SARIF/summary report. Example: "java,javascript"
52+
required: false
53+
not-package-types:
54+
description: Comma-separated list of package types to exclude from the SARIF/summary report. Example: "os,alpine"
55+
required: false
56+
exclude-accepted:
57+
description: Exclude vulnerabilities that have accepted risks from SARIF/summary report. true/false
58+
default: "false"
59+
required: false
5060
group-by-package:
5161
description: Enable grouping the vulnerabilities in the SARIF report by package.
5262
default: "false"

index.ts

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import * as core from '@actions/core';
22
import fs from 'fs';
33
import { generateSARIFReport } from './src/sarif';
4-
import { cliScannerName, cliScannerResult, cliScannerURL, executeScan, numericPriorityForSeverity, pullScanner, ScanExecutionResult, ScanMode } from './src/scanner';
4+
import { cliScannerName, cliScannerResult, cliScannerURL, executeScan, pullScanner, ScanExecutionResult, ScanMode } from './src/scanner';
55
import { ActionInputs, defaultSecureEndpoint } from './src/action';
66
import { generateSummary } from './src/summary';
7-
import { Report } from './src/report';
7+
import { Report, FilterOptions, Severity } from './src/report';
8+
9+
function parseCsvList(str?: string): string[] {
10+
if (!str) return [];
11+
return str.split(",").map(s => s.trim()).filter(s => !!s);
12+
}
813

914
export class ExecutionError extends Error {
1015
constructor(stdout: string, stderr: string) {
@@ -34,7 +39,6 @@ export async function run() {
3439
retCode = scanResult.ReturnCode;
3540
if (retCode == 0 || retCode == 1) {
3641
// Transform Scan Results to other formats such as SARIF
37-
3842
if (opts.mode == ScanMode.vm) {
3943
await processScanResult(scanResult, opts);
4044
}
@@ -57,22 +61,12 @@ export async function run() {
5761

5862
} catch (error) {
5963
if (core.getInput('stop-on-processing-error') == 'true') {
60-
core.setFailed("Unexpected error");
64+
core.setFailed(`Unexpected error: ${error instanceof Error ? error.stack : String(error)}`);
6165
}
62-
core.error(error as string);
66+
core.error(`Unexpected error: ${error instanceof Error ? error.stack : String(error)}`);
6367
}
6468
}
6569

66-
67-
export function filterResult(report: Report, severity: string) {
68-
let filter_num: number = numericPriorityForSeverity(severity) ?? 5;
69-
70-
report.result.packages.forEach(pkg => {
71-
if (pkg.vulns) pkg.vulns = pkg.vulns.filter((vuln) => (numericPriorityForSeverity(vuln.severity.value) ?? 5) <= filter_num);
72-
});
73-
return report;
74-
}
75-
7670
export async function processScanResult(result: ScanExecutionResult, opts: ActionInputs) {
7771
writeReport(result.Output);
7872

@@ -85,23 +79,29 @@ export async function processScanResult(result: ScanExecutionResult, opts: Actio
8579
}
8680

8781
if (report) {
88-
if (opts.severityAtLeast) {
89-
report = filterResult(report, opts.severityAtLeast);
90-
}
9182

92-
generateSARIFReport(report, opts.groupByPackage);
83+
// Prepara los filtros desde opts:
84+
const filters: FilterOptions = {
85+
minSeverity: (opts.severityAtLeast && opts.severityAtLeast.toLowerCase() !== "any")
86+
? opts.severityAtLeast.toLowerCase() as Severity
87+
: undefined,
88+
packageTypes: parseCsvList(opts.packageTypes),
89+
notPackageTypes: parseCsvList(opts.notPackageTypes),
90+
excludeAccepted: opts.excludeAccepted,
91+
};
92+
93+
generateSARIFReport(report, opts.groupByPackage, filters);
9394

9495
if (!opts.skipSummary) {
9596
core.info("Generating Summary...")
96-
97-
await generateSummary(opts, report);
98-
97+
await generateSummary(opts, report, filters);
9998
} else {
10099
core.info("Skipping Summary...")
101100
}
102101
}
103102
}
104103

104+
105105
export {
106106
cliScannerURL,
107107
defaultSecureEndpoint,
@@ -111,6 +111,7 @@ export {
111111
cliScannerResult,
112112
};
113113

114+
114115
if (require.main === module) {
115116
run();
116117
}

src/action.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import * as core from '@actions/core';
22
import { cliScannerResult, cliScannerURL, ComposeFlags, ScanMode, scannerURLForVersion } from './scanner';
3+
import { Severity, SeverityNames } from './report';
34

45
export const defaultSecureEndpoint = "https://secure.sysdig.com/"
56

@@ -21,6 +22,9 @@ interface ActionInputParameters {
2122
sysdigSecureURL: string;
2223
sysdigSkipTLS: boolean;
2324
severityAtLeast?: string;
25+
packageTypes?: string;
26+
notPackageTypes?: string;
27+
excludeAccepted?: boolean;
2428
groupByPackage: boolean;
2529
extraParameters: string;
2630
mode: ScanMode;
@@ -71,6 +75,9 @@ export class ActionInputs {
7175
sysdigSecureURL: core.getInput('sysdig-secure-url') || defaultSecureEndpoint,
7276
sysdigSkipTLS: core.getInput('sysdig-skip-tls') == 'true',
7377
severityAtLeast: core.getInput('severity-at-least') || undefined,
78+
packageTypes: core.getInput('package-types') || undefined,
79+
notPackageTypes: core.getInput('not-package-types') || undefined,
80+
excludeAccepted: core.getInput('exclude-accepted') === 'true',
7481
groupByPackage: core.getInput('group-by-package') == 'true',
7582
extraParameters: core.getInput('extra-parameters'),
7683
mode: ScanMode.fromString(core.getInput('mode')) || ScanMode.vm,
@@ -120,6 +127,16 @@ export class ActionInputs {
120127
return this.params.severityAtLeast
121128
}
122129

130+
get packageTypes() {
131+
return this.params.packageTypes;
132+
}
133+
get notPackageTypes() {
134+
return this.params.notPackageTypes;
135+
}
136+
get excludeAccepted() {
137+
return this.params.excludeAccepted;
138+
}
139+
123140
get imageTag() {
124141
return this.params.imageTag
125142
}
@@ -143,6 +160,11 @@ export class ActionInputs {
143160
core.setFailed("iac-scan-path can't be empty, please specify the path you want to scan your manifest resources.");
144161
throw new Error("iac-scan-path can't be empty, please specify the path you want to scan your manifest resources.");
145162
}
163+
164+
if (params.severityAtLeast && params.severityAtLeast != "any" && !SeverityNames.includes(params.severityAtLeast.toLowerCase() as Severity)) {
165+
core.setFailed(`Invalid severity-at-least value "${params.severityAtLeast}". Allowed values: any, critical, high, medium, low, negligible.`);
166+
throw new Error(`Invalid severity-at-least value "${params.severityAtLeast}". Allowed values: any, critical, high, medium, low, negligible.`);
167+
}
146168
}
147169

148170
// FIXME(fede) this also modifies the opts.cliScannerURL, which is something we don't want
@@ -251,6 +273,18 @@ export class ActionInputs {
251273
core.info(`Severity level: ${this.params.severityAtLeast}`);
252274
}
253275

276+
if (this.params.packageTypes) {
277+
core.info(`Package types included: ${this.params.packageTypes}`);
278+
}
279+
280+
if (this.params.notPackageTypes) {
281+
core.info(`Package types excluded: ${this.params.notPackageTypes}`);
282+
}
283+
284+
if (this.params.excludeAccepted !== undefined) {
285+
core.info(`Exclude vulnerabilities with accepted risks: ${this.params.excludeAccepted}`);
286+
}
287+
254288
core.info('Analyzing image: ' + this.params.imageTag);
255289

256290
if (this.params.overridePullString) {

src/report.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export interface Package {
7070

7171
export interface Vuln {
7272
name: string
73-
severity: Severity
73+
severity: SarifSeverity
7474
cvssScore: CvssScore
7575
disclosureDate: string
7676
solutionDate?: string
@@ -81,11 +81,22 @@ export interface Vuln {
8181
acceptedRisks?: AcceptedRisk[]
8282
}
8383

84-
export interface Severity {
84+
export interface FilterOptions {
85+
minSeverity?: Severity;
86+
packageTypes?: string[];
87+
notPackageTypes?: string[];
88+
excludeAccepted?: boolean;
89+
}
90+
91+
92+
export interface SarifSeverity {
8593
value: string
8694
sourceName: string
8795
}
8896

97+
export const SeverityNames = ["critical", "high", "medium", "low", "negligible"] as const;
98+
export type Severity = typeof SeverityNames[number];
99+
89100
export interface CvssScore {
90101
value: Value
91102
sourceName: string
@@ -199,3 +210,29 @@ export interface RiskAcceptanceDefinition {
199210
updatedAt: string
200211
}
201212

213+
const severityOrder = ["negligible", "low", "medium", "high", "critical"];
214+
215+
function isSeverityGte(a: string, b: string): boolean {
216+
return severityOrder.indexOf(a.toLocaleLowerCase()) >= severityOrder.indexOf(b.toLocaleLowerCase());
217+
}
218+
219+
export function filterPackages(pkgs: Package[], filters: FilterOptions): Package[] {
220+
return pkgs
221+
.filter(pkg => {
222+
const pkgType = pkg.type?.toLowerCase();
223+
if (filters.packageTypes && filters.packageTypes.length > 0 &&
224+
!filters.packageTypes.map(t => t.toLowerCase()).includes(pkgType)) return false;
225+
if (filters.notPackageTypes && filters.notPackageTypes.length > 0 &&
226+
filters.notPackageTypes.map(t => t.toLowerCase()).includes(pkgType)) return false;
227+
return true;
228+
})
229+
.map(pkg => {
230+
let vulns = pkg.vulns?.filter(vuln => {
231+
if (filters.minSeverity && !isSeverityGte(vuln.severity.value, filters.minSeverity)) return false;
232+
if (filters.excludeAccepted && Array.isArray(vuln.acceptedRisks) && vuln.acceptedRisks.length > 0) return false;
233+
return true;
234+
}) || [];
235+
return { ...pkg, vulns };
236+
})
237+
.filter(pkg => pkg.vulns && pkg.vulns.length > 0);
238+
}

src/sarif.ts

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import * as core from '@actions/core';
22
import fs from 'fs';
3-
import { Package, Report, Vuln } from './report';
3+
import { Package, Report, FilterOptions, Vuln, filterPackages } from './report';
44

55
import { version } from '../package.json';
6-
import { numericPriorityForSeverity } from './scanner';
76
const toolVersion = `${version}`;
87
const dottedQuadToolVersion = `${version}.0`;
98

@@ -47,20 +46,28 @@ interface SARIFRule {
4746
};
4847
}
4948

50-
export function generateSARIFReport(data: Report, groupByPackage: boolean) {
51-
let sarifOutput = vulnerabilities2SARIF(data, groupByPackage);
49+
export function generateSARIFReport(data: Report, groupByPackage: boolean, filters?: FilterOptions) {
50+
let sarifOutput = vulnerabilities2SARIF(data, groupByPackage, filters);
5251
core.setOutput("sarifReport", "./sarif.json");
5352
fs.writeFileSync("./sarif.json", JSON.stringify(sarifOutput, null, 2));
5453
}
5554

56-
export function vulnerabilities2SARIF(data: Report, groupByPackage: boolean) {
55+
export function vulnerabilities2SARIF(
56+
data: Report,
57+
groupByPackage: boolean,
58+
filters?: FilterOptions
59+
) {
60+
61+
const filteredPackages = filterPackages(data.result.packages, filters ?? {});
62+
const filteredData = { ...data, result: { ...data.result, packages: filteredPackages } };
63+
5764
let rules: SARIFRule[] = [];
5865
let results: SARIFResult[] = [];
5966

6067
if (groupByPackage) {
61-
[rules, results] = vulnerabilities2SARIFResByPackage(data)
68+
[rules, results] = vulnerabilities2SARIFResByPackage(filteredData)
6269
} else {
63-
[rules, results] = vulnerabilities2SARIFRes(data)
70+
[rules, results] = vulnerabilities2SARIFRes(filteredData)
6471
}
6572

6673
const runs = [{
@@ -108,6 +115,22 @@ export function vulnerabilities2SARIF(data: Report, groupByPackage: boolean) {
108115
return (sarifOutput);
109116
}
110117

118+
function numericPriorityForSeverity(severity: string): number | undefined {
119+
switch (severity.toLowerCase()) {
120+
case 'critical':
121+
return 0
122+
case 'high':
123+
return 1
124+
case 'medium':
125+
return 2
126+
case 'low':
127+
return 3
128+
case 'negligible':
129+
return 4
130+
case 'any':
131+
return 5
132+
}
133+
}
111134

112135
function vulnerabilities2SARIFResByPackage(data: Report): [SARIFRule[], SARIFResult[]] {
113136
let rules: SARIFRule[] = [];

src/scanner.ts

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -116,23 +116,6 @@ export function scannerURLForVersion(version: string): string {
116116
return `${cliScannerURLBase}/${version}/${cliScannerOS}/${cliScannerArch}/${cliScannerName}`;
117117

118118
}
119-
export function numericPriorityForSeverity(severity: string): number | undefined {
120-
switch (severity.toLowerCase()) {
121-
case 'critical':
122-
return 0
123-
case 'high':
124-
return 1
125-
case 'medium':
126-
return 2
127-
case 'low':
128-
return 3
129-
case 'negligible':
130-
return 4
131-
case 'any':
132-
return 5
133-
}
134-
}
135-
136119

137120
function getRunArch() {
138121
let arch = "unknown";

0 commit comments

Comments
 (0)