From e778c5bfa22d2a5ba7deb3f89b67cf98ea382f1e Mon Sep 17 00:00:00 2001 From: thtri Date: Thu, 24 Jul 2025 14:22:05 +0200 Subject: [PATCH] CxOne: support Critical severity (#5418) --- cmd/checkmarxOneExecuteScan.go | 36 ++++++++++++++++++ cmd/checkmarxOneExecuteScan_generated.go | 37 ++++++++++++++++--- pkg/checkmarxone/checkmarxone.go | 1 + pkg/checkmarxone/reporting.go | 14 +++++++ pkg/checkmarxone/reporting_test.go | 9 ++++- .../metadata/checkmarxOneExecuteScan.yaml | 32 +++++++++++++--- 6 files changed, 117 insertions(+), 12 deletions(-) diff --git a/cmd/checkmarxOneExecuteScan.go b/cmd/checkmarxOneExecuteScan.go index 73180522a..74070215d 100644 --- a/cmd/checkmarxOneExecuteScan.go +++ b/cmd/checkmarxOneExecuteScan.go @@ -808,6 +808,7 @@ func (c *checkmarxOneExecuteScanHelper) getDetailedResults(scan *checkmarxOne.Sc resultMap["Preset"] = scanmeta.PresetName resultMap["DeepLink"] = fmt.Sprintf("%v/projects/%v/overview?branch=%v", c.config.ServerURL, c.Project.ProjectID, url.QueryEscape(scan.Branch)) resultMap["ReportCreationTime"] = time.Now().String() + resultMap["Critical"] = map[string]int{} resultMap["High"] = map[string]int{} resultMap["Medium"] = map[string]int{} resultMap["Low"] = map[string]int{} @@ -817,6 +818,8 @@ func (c *checkmarxOneExecuteScanHelper) getDetailedResults(scan *checkmarxOne.Sc for _, result := range *results { key := "Information" switch result.Severity { + case "CRITICAL": + key = "Critical" case "HIGH": key = "High" case "MEDIUM": @@ -1075,20 +1078,29 @@ func (c *checkmarxOneExecuteScanHelper) enforceThresholds(results *map[string]in insecureResults := []string{} insecure := false + cxCriticalThreshold := c.config.VulnerabilityThresholdCritical cxHighThreshold := c.config.VulnerabilityThresholdHigh cxMediumThreshold := c.config.VulnerabilityThresholdMedium cxLowThreshold := c.config.VulnerabilityThresholdLow cxLowThresholdPerQuery := c.config.VulnerabilityThresholdLowPerQuery cxLowThresholdPerQueryMax := c.config.VulnerabilityThresholdLowPerQueryMax + criticalValue := (*results)["Critical"].(map[string]int)["NotFalsePositive"] highValue := (*results)["High"].(map[string]int)["NotFalsePositive"] mediumValue := (*results)["Medium"].(map[string]int)["NotFalsePositive"] lowValue := (*results)["Low"].(map[string]int)["NotFalsePositive"] var unit string + criticalViolation := "" highViolation := "" mediumViolation := "" lowViolation := "" if c.config.VulnerabilityThresholdUnit == "percentage" { unit = "%" + criticalAudited := (*results)["Critical"].(map[string]int)["Issues"] - (*results)["Critical"].(map[string]int)["NotFalsePositive"] + criticalOverall := (*results)["Critical"].(map[string]int)["Issues"] + if criticalOverall == 0 { + criticalAudited = 1 + criticalOverall = 1 + } highAudited := (*results)["High"].(map[string]int)["Issues"] - (*results)["High"].(map[string]int)["NotFalsePositive"] highOverall := (*results)["High"].(map[string]int)["Issues"] if highOverall == 0 { @@ -1107,10 +1119,15 @@ func (c *checkmarxOneExecuteScanHelper) enforceThresholds(results *map[string]in lowAudited = 1 lowOverall = 1 } + criticalValue = int(float32(criticalAudited) / float32(criticalOverall) * 100.0) highValue = int(float32(highAudited) / float32(highOverall) * 100.0) mediumValue = int(float32(mediumAudited) / float32(mediumOverall) * 100.0) lowValue = int(float32(lowAudited) / float32(lowOverall) * 100.0) + if criticalValue < cxCriticalThreshold { + insecure = true + criticalViolation = fmt.Sprintf("<-- %v %v deviation", cxCriticalThreshold-criticalValue, unit) + } if highValue < cxHighThreshold { insecure = true highViolation = fmt.Sprintf("<-- %v %v deviation", cxHighThreshold-highValue, unit) @@ -1148,6 +1165,10 @@ func (c *checkmarxOneExecuteScanHelper) enforceThresholds(results *map[string]in } if c.config.VulnerabilityThresholdUnit == "absolute" { unit = " findings" + if criticalValue > cxCriticalThreshold { + insecure = true + criticalViolation = fmt.Sprintf("<-- %v%v deviation", criticalValue-cxCriticalThreshold, unit) + } if highValue > cxHighThreshold { insecure = true highViolation = fmt.Sprintf("<-- %v%v deviation", highValue-cxHighThreshold, unit) @@ -1162,9 +1183,17 @@ func (c *checkmarxOneExecuteScanHelper) enforceThresholds(results *map[string]in } } + criticalText := fmt.Sprintf("Critical %v%v %v", criticalValue, unit, criticalViolation) highText := fmt.Sprintf("High %v%v %v", highValue, unit, highViolation) mediumText := fmt.Sprintf("Medium %v%v %v", mediumValue, unit, mediumViolation) lowText := fmt.Sprintf("Low %v%v %v", lowValue, unit, lowViolation) + if len(criticalViolation) > 0 { + insecureResults = append(insecureResults, criticalText) + log.Entry().Error(criticalText) + } else { + neutralResults = append(neutralResults, criticalText) + log.Entry().Info(criticalText) + } if len(highViolation) > 0 { insecureResults = append(insecureResults, highText) log.Entry().Error(highText) @@ -1191,6 +1220,13 @@ func (c *checkmarxOneExecuteScanHelper) enforceThresholds(results *map[string]in } func (c *checkmarxOneExecuteScanHelper) reportToInflux(results *map[string]interface{}) { + c.influx.checkmarxOne_data.fields.critical_issues = (*results)["Critical"].(map[string]int)["Issues"] + c.influx.checkmarxOne_data.fields.critical_not_false_postive = (*results)["Critical"].(map[string]int)["NotFalsePositive"] + c.influx.checkmarxOne_data.fields.critical_not_exploitable = (*results)["Critical"].(map[string]int)["NotExploitable"] + c.influx.checkmarxOne_data.fields.critical_confirmed = (*results)["Critical"].(map[string]int)["Confirmed"] + c.influx.checkmarxOne_data.fields.critical_urgent = (*results)["Critical"].(map[string]int)["Urgent"] + c.influx.checkmarxOne_data.fields.critical_proposed_not_exploitable = (*results)["Critical"].(map[string]int)["ProposedNotExploitable"] + c.influx.checkmarxOne_data.fields.critical_to_verify = (*results)["Critical"].(map[string]int)["ToVerify"] c.influx.checkmarxOne_data.fields.high_issues = (*results)["High"].(map[string]int)["Issues"] c.influx.checkmarxOne_data.fields.high_not_false_postive = (*results)["High"].(map[string]int)["NotFalsePositive"] diff --git a/cmd/checkmarxOneExecuteScan_generated.go b/cmd/checkmarxOneExecuteScan_generated.go index 2d6deb1b5..7fad1a879 100644 --- a/cmd/checkmarxOneExecuteScan_generated.go +++ b/cmd/checkmarxOneExecuteScan_generated.go @@ -54,6 +54,7 @@ type checkmarxOneExecuteScanOptions struct { ClientID string `json:"clientId,omitempty"` VerifyOnly bool `json:"verifyOnly,omitempty"` VulnerabilityThresholdEnabled bool `json:"vulnerabilityThresholdEnabled,omitempty"` + VulnerabilityThresholdCritical int `json:"vulnerabilityThresholdCritical,omitempty"` VulnerabilityThresholdHigh int `json:"vulnerabilityThresholdHigh,omitempty"` VulnerabilityThresholdMedium int `json:"vulnerabilityThresholdMedium,omitempty"` VulnerabilityThresholdLow int `json:"vulnerabilityThresholdLow,omitempty"` @@ -76,6 +77,13 @@ type checkmarxOneExecuteScanInflux struct { } checkmarxOne_data struct { fields struct { + critical_issues int + critical_not_false_postive int + critical_not_exploitable int + critical_confirmed int + critical_urgent int + critical_proposed_not_exploitable int + critical_to_verify int high_issues int high_not_false_postive int high_not_exploitable int @@ -134,6 +142,13 @@ func (i *checkmarxOneExecuteScanInflux) persist(path, resourceName string) { value interface{} }{ {valType: config.InfluxField, measurement: "step_data", name: "checkmarxOne", value: i.step_data.fields.checkmarxOne}, + {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "critical_issues", value: i.checkmarxOne_data.fields.critical_issues}, + {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "critical_not_false_postive", value: i.checkmarxOne_data.fields.critical_not_false_postive}, + {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "critical_not_exploitable", value: i.checkmarxOne_data.fields.critical_not_exploitable}, + {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "critical_confirmed", value: i.checkmarxOne_data.fields.critical_confirmed}, + {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "critical_urgent", value: i.checkmarxOne_data.fields.critical_urgent}, + {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "critical_proposed_not_exploitable", value: i.checkmarxOne_data.fields.critical_proposed_not_exploitable}, + {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "critical_to_verify", value: i.checkmarxOne_data.fields.critical_to_verify}, {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "high_issues", value: i.checkmarxOne_data.fields.high_issues}, {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "high_not_false_postive", value: i.checkmarxOne_data.fields.high_not_false_postive}, {valType: config.InfluxField, measurement: "checkmarxOne_data", name: "high_not_exploitable", value: i.checkmarxOne_data.fields.high_not_exploitable}, @@ -400,11 +415,12 @@ func addCheckmarxOneExecuteScanFlags(cmd *cobra.Command, stepConfig *checkmarxOn cmd.Flags().StringVar(&stepConfig.ClientID, "clientId", os.Getenv("PIPER_clientId"), "The username to authenticate") cmd.Flags().BoolVar(&stepConfig.VerifyOnly, "verifyOnly", false, "Whether the step shall only apply verification checks or whether it does a full scan and check cycle") cmd.Flags().BoolVar(&stepConfig.VulnerabilityThresholdEnabled, "vulnerabilityThresholdEnabled", true, "Whether the thresholds are enabled or not. If enabled the build will be set to `vulnerabilityThresholdResult` in case a specific threshold value is exceeded") - cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdHigh, "vulnerabilityThresholdHigh", 100, "The specific threshold for high severity findings") - cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdMedium, "vulnerabilityThresholdMedium", 100, "The specific threshold for medium severity findings") - cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdLow, "vulnerabilityThresholdLow", 10, "The specific threshold for low severity findings") - cmd.Flags().BoolVar(&stepConfig.VulnerabilityThresholdLowPerQuery, "vulnerabilityThresholdLowPerQuery", false, "Flag to activate/deactivate the threshold of low severity findings per query") - cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdLowPerQueryMax, "vulnerabilityThresholdLowPerQueryMax", 10, "Upper threshold of low severity findings per query (in absolute number)") + cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdCritical, "vulnerabilityThresholdCritical", 100, "The specific threshold for Critical severity findings") + cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdHigh, "vulnerabilityThresholdHigh", 100, "The specific threshold for High severity findings") + cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdMedium, "vulnerabilityThresholdMedium", 100, "The specific threshold for Medium severity findings") + cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdLow, "vulnerabilityThresholdLow", 10, "The specific threshold for Low severity findings") + cmd.Flags().BoolVar(&stepConfig.VulnerabilityThresholdLowPerQuery, "vulnerabilityThresholdLowPerQuery", false, "Flag to activate/deactivate the threshold of Low severity findings per query") + cmd.Flags().IntVar(&stepConfig.VulnerabilityThresholdLowPerQueryMax, "vulnerabilityThresholdLowPerQueryMax", 10, "Upper threshold of Low severity findings per query (in absolute number)") cmd.Flags().StringVar(&stepConfig.VulnerabilityThresholdResult, "vulnerabilityThresholdResult", `FAILURE`, "The result of the build in case thresholds are enabled and exceeded") cmd.Flags().StringVar(&stepConfig.VulnerabilityThresholdUnit, "vulnerabilityThresholdUnit", `percentage`, "The unit for the threshold to apply.") cmd.Flags().BoolVar(&stepConfig.IsOptimizedAndScheduled, "isOptimizedAndScheduled", false, "Whether the pipeline runs in optimized mode and the current execution is a scheduled one") @@ -782,6 +798,15 @@ func checkmarxOneExecuteScanMetadata() config.StepData { Aliases: []config.Alias{}, Default: true, }, + { + Name: "vulnerabilityThresholdCritical", + ResourceRef: []config.ResourceReference{}, + Scope: []string{"PARAMETERS", "STAGES", "STEPS"}, + Type: "int", + Mandatory: false, + Aliases: []config.Alias{}, + Default: 100, + }, { Name: "vulnerabilityThresholdHigh", ResourceRef: []config.ResourceReference{}, @@ -891,7 +916,7 @@ func checkmarxOneExecuteScanMetadata() config.StepData { Type: "influx", Parameters: []map[string]interface{}{ {"name": "step_data", "fields": []map[string]string{{"name": "checkmarxOne"}}}, - {"name": "checkmarxOne_data", "fields": []map[string]string{{"name": "high_issues"}, {"name": "high_not_false_postive"}, {"name": "high_not_exploitable"}, {"name": "high_confirmed"}, {"name": "high_urgent"}, {"name": "high_proposed_not_exploitable"}, {"name": "high_to_verify"}, {"name": "medium_issues"}, {"name": "medium_not_false_postive"}, {"name": "medium_not_exploitable"}, {"name": "medium_confirmed"}, {"name": "medium_urgent"}, {"name": "medium_proposed_not_exploitable"}, {"name": "medium_to_verify"}, {"name": "low_issues"}, {"name": "low_not_false_postive"}, {"name": "low_not_exploitable"}, {"name": "low_confirmed"}, {"name": "low_urgent"}, {"name": "low_proposed_not_exploitable"}, {"name": "low_to_verify"}, {"name": "information_issues"}, {"name": "information_not_false_postive"}, {"name": "information_not_exploitable"}, {"name": "information_confirmed"}, {"name": "information_urgent"}, {"name": "information_proposed_not_exploitable"}, {"name": "information_to_verify"}, {"name": "lines_of_code_scanned"}, {"name": "files_scanned"}, {"name": "initiator_name"}, {"name": "owner"}, {"name": "scan_id"}, {"name": "project_id"}, {"name": "projectName"}, {"name": "group"}, {"name": "group_full_path_on_report_date"}, {"name": "scan_start"}, {"name": "scan_time"}, {"name": "tool_version"}, {"name": "scan_type"}, {"name": "preset"}, {"name": "deep_link"}, {"name": "report_creation_time"}}}, + {"name": "checkmarxOne_data", "fields": []map[string]string{{"name": "critical_issues"}, {"name": "critical_not_false_postive"}, {"name": "critical_not_exploitable"}, {"name": "critical_confirmed"}, {"name": "critical_urgent"}, {"name": "critical_proposed_not_exploitable"}, {"name": "critical_to_verify"}, {"name": "high_issues"}, {"name": "high_not_false_postive"}, {"name": "high_not_exploitable"}, {"name": "high_confirmed"}, {"name": "high_urgent"}, {"name": "high_proposed_not_exploitable"}, {"name": "high_to_verify"}, {"name": "medium_issues"}, {"name": "medium_not_false_postive"}, {"name": "medium_not_exploitable"}, {"name": "medium_confirmed"}, {"name": "medium_urgent"}, {"name": "medium_proposed_not_exploitable"}, {"name": "medium_to_verify"}, {"name": "low_issues"}, {"name": "low_not_false_postive"}, {"name": "low_not_exploitable"}, {"name": "low_confirmed"}, {"name": "low_urgent"}, {"name": "low_proposed_not_exploitable"}, {"name": "low_to_verify"}, {"name": "information_issues"}, {"name": "information_not_false_postive"}, {"name": "information_not_exploitable"}, {"name": "information_confirmed"}, {"name": "information_urgent"}, {"name": "information_proposed_not_exploitable"}, {"name": "information_to_verify"}, {"name": "lines_of_code_scanned"}, {"name": "files_scanned"}, {"name": "initiator_name"}, {"name": "owner"}, {"name": "scan_id"}, {"name": "project_id"}, {"name": "projectName"}, {"name": "group"}, {"name": "group_full_path_on_report_date"}, {"name": "scan_start"}, {"name": "scan_time"}, {"name": "tool_version"}, {"name": "scan_type"}, {"name": "preset"}, {"name": "deep_link"}, {"name": "report_creation_time"}}}, }, }, { diff --git a/pkg/checkmarxone/checkmarxone.go b/pkg/checkmarxone/checkmarxone.go index 462b5cf00..f5be1358f 100644 --- a/pkg/checkmarxone/checkmarxone.go +++ b/pkg/checkmarxone/checkmarxone.go @@ -1356,6 +1356,7 @@ func (sys *SystemInstance) RequestNewReportV2(scanID, reportType string) (string "filters": map[string][]string{ "scanners": {"sast"}, "severities": { + "critical", "high", "medium", "low", diff --git a/pkg/checkmarxone/reporting.go b/pkg/checkmarxone/reporting.go index dbf4613b9..cd58e2108 100644 --- a/pkg/checkmarxone/reporting.go +++ b/pkg/checkmarxone/reporting.go @@ -94,6 +94,13 @@ func CreateCustomReport(data *map[string]interface{}, insecure, neutral []string WithCounter: false, } detailRows := []reporting.OverviewRow{ + {Description: "Critical issues", Details: fmt.Sprint((*data)["Critical"].(map[string]int)["Issues"])}, + {Description: "Critical not false positive issues", Details: fmt.Sprint((*data)["Critical"].(map[string]int)["NotFalsePositive"])}, + {Description: "Critical not exploitable issues", Details: fmt.Sprint((*data)["Critical"].(map[string]int)["NotExploitable"])}, + {Description: "Critical confirmed issues", Details: fmt.Sprint((*data)["Critical"].(map[string]int)["Confirmed"])}, + {Description: "Critical urgent issues", Details: fmt.Sprint((*data)["Critical"].(map[string]int)["Urgent"])}, + {Description: "Critical proposed not exploitable issues", Details: fmt.Sprint((*data)["Critical"].(map[string]int)["ProposedNotExploitable"])}, + {Description: "Critical to verify issues", Details: fmt.Sprint((*data)["Critical"].(map[string]int)["ToVerify"])}, {Description: "High issues", Details: fmt.Sprint((*data)["High"].(map[string]int)["Issues"])}, {Description: "High not false positive issues", Details: fmt.Sprint((*data)["High"].(map[string]int)["NotFalsePositive"])}, {Description: "High not exploitable issues", Details: fmt.Sprint((*data)["High"].(map[string]int)["NotExploitable"])}, @@ -152,6 +159,13 @@ func CreateJSONHeaderReport(data *map[string]interface{}) CheckmarxOneReportData } findings := []Finding{} + // Critical + criticalFindings := Finding{} + criticalFindings.ClassificationName = "Critical" + criticalFindings.Total = (*data)["Critical"].(map[string]int)["Issues"] + criticalAudited := (*data)["Critical"].(map[string]int)["Issues"] - (*data)["Critical"].(map[string]int)["NotFalsePositive"] + criticalFindings.Audited = &criticalAudited + findings = append(findings, criticalFindings) // High highFindings := Finding{} highFindings.ClassificationName = "High" diff --git a/pkg/checkmarxone/reporting_test.go b/pkg/checkmarxone/reporting_test.go index 5cf735d09..278b9c37d 100644 --- a/pkg/checkmarxone/reporting_test.go +++ b/pkg/checkmarxone/reporting_test.go @@ -21,13 +21,20 @@ func TestCreateJSONReport(t *testing.T) { resultMap["ProjectId"] = `f5702f86-b396-417f-82e2-4949a55d5382` resultMap["ScanId"] = `21e40b36-0dd7-48e5-9768-da1a8f36c907` + resultMap["Critical"] = map[string]int{} resultMap["High"] = map[string]int{} resultMap["Medium"] = map[string]int{} resultMap["Low"] = map[string]int{} resultMap["Information"] = map[string]int{} + submap := map[string]int{} submap["Issues"] = 10 submap["NotFalsePositive"] = 10 + resultMap["Critical"] = submap + + submap = map[string]int{} + submap["Issues"] = 10 + submap["NotFalsePositive"] = 10 resultMap["High"] = submap submap = map[string]int{} @@ -74,7 +81,7 @@ func TestCreateJSONReport(t *testing.T) { assert.Equal(t, "v1", reportingData.ToolVersion) assert.Equal(t, "Incremental", reportingData.ScanType) - lowList := (*reportingData.Findings)[2].LowPerQuery + lowList := (*reportingData.Findings)[3].LowPerQuery lowListLen := len(*lowList) assert.Equal(t, 2, lowListLen) diff --git a/resources/metadata/checkmarxOneExecuteScan.yaml b/resources/metadata/checkmarxOneExecuteScan.yaml index ff89ea18b..314b18687 100644 --- a/resources/metadata/checkmarxOneExecuteScan.yaml +++ b/resources/metadata/checkmarxOneExecuteScan.yaml @@ -327,9 +327,17 @@ spec: - STAGES - STEPS default: true + - name: vulnerabilityThresholdCritical + type: int + description: The specific threshold for Critical severity findings + scope: + - PARAMETERS + - STAGES + - STEPS + default: 100 - name: vulnerabilityThresholdHigh type: int - description: The specific threshold for high severity findings + description: The specific threshold for High severity findings scope: - PARAMETERS - STAGES @@ -337,7 +345,7 @@ spec: default: 100 - name: vulnerabilityThresholdMedium type: int - description: The specific threshold for medium severity findings + description: The specific threshold for Medium severity findings scope: - PARAMETERS - STAGES @@ -345,7 +353,7 @@ spec: default: 100 - name: vulnerabilityThresholdLow type: int - description: The specific threshold for low severity findings + description: The specific threshold for Low severity findings scope: - PARAMETERS - STAGES @@ -353,7 +361,7 @@ spec: default: 10 - name: vulnerabilityThresholdLowPerQuery type: bool - description: Flag to activate/deactivate the threshold of low severity findings per query + description: Flag to activate/deactivate the threshold of Low severity findings per query scope: - PARAMETERS - STAGES @@ -361,7 +369,7 @@ spec: default: false - name: vulnerabilityThresholdLowPerQueryMax type: int - description: Upper threshold of low severity findings per query (in absolute number) + description: Upper threshold of Low severity findings per query (in absolute number) scope: - PARAMETERS - STAGES @@ -427,6 +435,20 @@ spec: type: bool - name: checkmarxOne_data fields: + - name: critical_issues + type: int + - name: critical_not_false_postive + type: int + - name: critical_not_exploitable + type: int + - name: critical_confirmed + type: int + - name: critical_urgent + type: int + - name: critical_proposed_not_exploitable + type: int + - name: critical_to_verify + type: int - name: high_issues type: int - name: high_not_false_postive