From d723dbb6f81fed58979b7af14f78b2e74aecb52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Fabianski?= Date: Wed, 30 Aug 2023 16:02:25 +0200 Subject: [PATCH] feat: add severity computation details (#1195) * feat: add severity computation details * ci: update tests * fix: update tests and always include bool * fixup: update severity default to avoid empty values * feat: add severity weighting to saas * fix: clean up stale snapshots * feat: rename to SeverityMeta and add raw fields --------- Co-authored-by: elsapet --- pkg/commands/process/settings/settings.go | 10 ++++ pkg/report/output/privacy/privacy.go | 6 +- pkg/report/output/saas/saas.go | 7 ++- pkg/report/output/saas/types/types.go | 2 +- .../security/.snapshots/TestAddReportData | 27 ++++++++- .../.snapshots/TestAddReportDataWithSeverity | 14 ++++- .../security/.snapshots/TestCalculateSeverity | 59 +++++++++++++++++-- pkg/report/output/security/security.go | 33 ++++++++--- pkg/report/output/security/security_test.go | 3 +- pkg/report/output/security/types/types.go | 40 ++++++++----- 10 files changed, 163 insertions(+), 38 deletions(-) diff --git a/pkg/commands/process/settings/settings.go b/pkg/commands/process/settings/settings.go index a755dfe7b..acd9eec45 100644 --- a/pkg/commands/process/settings/settings.go +++ b/pkg/commands/process/settings/settings.go @@ -13,6 +13,8 @@ import ( "github.com/bearer/bearer/pkg/util/ignore" "github.com/bearer/bearer/pkg/util/output" "github.com/bearer/bearer/pkg/util/rego" + + globaltypes "github.com/bearer/bearer/pkg/types" ) var ( @@ -253,6 +255,14 @@ func (rule *Rule) PolicyType() bool { return rule.Type == "risk" } +func (rule *Rule) GetSeverity() string { + if rule.Severity == "" { + return globaltypes.LevelLow + } + + return rule.Severity +} + func (rule *Rule) Language() string { if rule.Languages == nil { return "secret" diff --git a/pkg/report/output/privacy/privacy.go b/pkg/report/output/privacy/privacy.go index 77431c45a..4a11b195c 100644 --- a/pkg/report/output/privacy/privacy.go +++ b/pkg/report/output/privacy/privacy.go @@ -177,7 +177,7 @@ func AddReportData(reportData *outputtypes.ReportData, config settings.Config) e } for _, ruleOutputFailure := range ruleOutput["local_rule_failure"] { - ruleSeverity := security.CalculateSeverity(ruleOutputFailure.CategoryGroups, rule.Severity, true) + ruleSeverity := security.CalculateSeverity(ruleOutputFailure.CategoryGroups, rule.GetSeverity(), true) key := buildKey(ruleOutputFailure.DataSubject, ruleOutputFailure.DataType) subjectRuleFailure, ok := subjectRuleFailures[key] @@ -193,7 +193,7 @@ func AddReportData(reportData *outputtypes.ReportData, config settings.Config) e } // count severity - switch ruleSeverity { + switch ruleSeverity.DisplaySeverity { case globaltypes.LevelCritical: subjectRuleFailure.CriticalRiskFindingCount += 1 case globaltypes.LevelHigh: @@ -233,7 +233,7 @@ func AddReportData(reportData *outputtypes.ReportData, config settings.Config) e } // count severity - switch ruleSeverity { + switch ruleSeverity.DisplaySeverity { case globaltypes.LevelCritical: thirdPartyDataSubject.CriticalRiskFindingCount += 1 case globaltypes.LevelHigh: diff --git a/pkg/report/output/saas/saas.go b/pkg/report/output/saas/saas.go index 4f68f3f35..b86f00cf6 100644 --- a/pkg/report/output/saas/saas.go +++ b/pkg/report/output/saas/saas.go @@ -38,7 +38,12 @@ func GetReport(reportData *types.ReportData, config settings.Config, ensureMeta saasFindingsBySeverity := make(map[string][]saas.SaasFinding) for _, severity := range maps.Keys(reportData.FindingsBySeverity) { for _, finding := range reportData.FindingsBySeverity[severity] { - saasFindingsBySeverity[severity] = append(saasFindingsBySeverity[severity], saas.SaasFinding{Finding: finding}) + saasFindingsBySeverity[severity] = append( + saasFindingsBySeverity[severity], + saas.SaasFinding{ + Finding: finding, + SeverityMeta: finding.SeverityMeta, + }) } } diff --git a/pkg/report/output/saas/types/types.go b/pkg/report/output/saas/types/types.go index d46344475..c7b517b2d 100644 --- a/pkg/report/output/saas/types/types.go +++ b/pkg/report/output/saas/types/types.go @@ -34,5 +34,5 @@ type BearerReport struct { type SaasFinding struct { securitytypes.Finding - // add any extra data to send to SaaS + SeverityMeta securitytypes.SeverityMeta `json:"severity_meta" yaml:"severity_meta"` } diff --git a/pkg/report/output/security/.snapshots/TestAddReportData b/pkg/report/output/security/.snapshots/TestAddReportData index 67aab782a..ef29e51ef 100644 --- a/pkg/report/output/security/.snapshots/TestAddReportData +++ b/pkg/report/output/security/.snapshots/TestAddReportData @@ -52,7 +52,19 @@ CodeExtract: (string) "", RawCodeExtract: ([]file.Line) { }, - SomeExtraField: (string) "" + SeverityMeta: (types.SeverityMeta) { + RuleSeverity: (string) (len=3) "low", + SensitiveDataCategories: ([]string) (len=3) { + (string) (len=3) "PII", + (string) (len=13) "Personal Data", + (string) (len=25) "Personal Data (Sensitive)" + }, + HasLocalDataTypes: (*bool)(true), + SensitiveDataCategoryWeighting: (int) 3, + RuleSeverityWeighting: (int) 2, + FinalWeighting: (int) 8, + DisplaySeverity: (string) (len=8) "critical" + } } }, (string) (len=4) "high": ([]types.Finding) (len=1) { @@ -103,7 +115,18 @@ CodeExtract: (string) "", RawCodeExtract: ([]file.Line) { }, - SomeExtraField: (string) "" + SeverityMeta: (types.SeverityMeta) { + RuleSeverity: (string) (len=6) "medium", + SensitiveDataCategories: ([]string) (len=2) { + (string) (len=3) "PII", + (string) (len=13) "Personal Data" + }, + HasLocalDataTypes: (*bool)(false), + SensitiveDataCategoryWeighting: (int) 2, + RuleSeverityWeighting: (int) 3, + FinalWeighting: (int) 5, + DisplaySeverity: (string) (len=4) "high" + } } } } diff --git a/pkg/report/output/security/.snapshots/TestAddReportDataWithSeverity b/pkg/report/output/security/.snapshots/TestAddReportDataWithSeverity index 83c8262a4..f73b86897 100644 --- a/pkg/report/output/security/.snapshots/TestAddReportDataWithSeverity +++ b/pkg/report/output/security/.snapshots/TestAddReportDataWithSeverity @@ -52,7 +52,19 @@ CodeExtract: (string) "", RawCodeExtract: ([]file.Line) { }, - SomeExtraField: (string) "" + SeverityMeta: (types.SeverityMeta) { + RuleSeverity: (string) (len=3) "low", + SensitiveDataCategories: ([]string) (len=3) { + (string) (len=3) "PII", + (string) (len=13) "Personal Data", + (string) (len=25) "Personal Data (Sensitive)" + }, + HasLocalDataTypes: (*bool)(true), + SensitiveDataCategoryWeighting: (int) 3, + RuleSeverityWeighting: (int) 2, + FinalWeighting: (int) 8, + DisplaySeverity: (string) (len=8) "critical" + } } } } diff --git a/pkg/report/output/security/.snapshots/TestCalculateSeverity b/pkg/report/output/security/.snapshots/TestCalculateSeverity index bdc410c0f..fcbcb5b27 100644 --- a/pkg/report/output/security/.snapshots/TestCalculateSeverity +++ b/pkg/report/output/security/.snapshots/TestCalculateSeverity @@ -1,7 +1,54 @@ -([]string) (len=5) { - (string) (len=8) "critical", - (string) (len=4) "high", - (string) (len=6) "medium", - (string) (len=7) "warning", - (string) (len=7) "warning" +([]types.SeverityMeta) (len=5) { + (types.SeverityMeta) { + RuleSeverity: (string) (len=3) "low", + SensitiveDataCategories: ([]string) (len=2) { + (string) (len=3) "PHI", + (string) (len=13) "Personal Data" + }, + HasLocalDataTypes: (*bool)(true), + SensitiveDataCategoryWeighting: (int) 3, + RuleSeverityWeighting: (int) 2, + FinalWeighting: (int) 8, + DisplaySeverity: (string) (len=8) "critical" + }, + (types.SeverityMeta) { + RuleSeverity: (string) (len=3) "low", + SensitiveDataCategories: ([]string) (len=1) { + (string) (len=25) "Personal Data (Sensitive)" + }, + HasLocalDataTypes: (*bool)(false), + SensitiveDataCategoryWeighting: (int) 3, + RuleSeverityWeighting: (int) 2, + FinalWeighting: (int) 5, + DisplaySeverity: (string) (len=4) "high" + }, + (types.SeverityMeta) { + RuleSeverity: (string) (len=3) "low", + SensitiveDataCategories: ([]string) (len=1) { + (string) (len=13) "Personal Data" + }, + HasLocalDataTypes: (*bool)(false), + SensitiveDataCategoryWeighting: (int) 2, + RuleSeverityWeighting: (int) 2, + FinalWeighting: (int) 4, + DisplaySeverity: (string) (len=6) "medium" + }, + (types.SeverityMeta) { + RuleSeverity: (string) (len=7) "warning", + SensitiveDataCategories: ([]string) , + HasLocalDataTypes: (*bool)(), + SensitiveDataCategoryWeighting: (int) 0, + RuleSeverityWeighting: (int) 0, + FinalWeighting: (int) 0, + DisplaySeverity: (string) (len=7) "warning" + }, + (types.SeverityMeta) { + RuleSeverity: (string) (len=7) "warning", + SensitiveDataCategories: ([]string) , + HasLocalDataTypes: (*bool)(), + SensitiveDataCategoryWeighting: (int) 0, + RuleSeverityWeighting: (int) 0, + FinalWeighting: (int) 0, + DisplaySeverity: (string) (len=7) "warning" + } } diff --git a/pkg/report/output/security/security.go b/pkg/report/output/security/security.go index 5da0c9672..5723dfce1 100644 --- a/pkg/report/output/security/security.go +++ b/pkg/report/output/security/security.go @@ -228,9 +228,11 @@ func evaluateRules( OldFingerprint: oldFingerprint, } - severity := CalculateSeverity(finding.CategoryGroups, rule.Severity, output.IsLocal != nil && *output.IsLocal) + severityWeighting := CalculateSeverity(finding.CategoryGroups, rule.GetSeverity(), output.IsLocal != nil && *output.IsLocal) + severity := severityWeighting.DisplaySeverity if config.Report.Severity[severity] { + finding.SeverityMeta = severityWeighting outputFindings[severity] = append(outputFindings[severity], finding) } } @@ -389,9 +391,12 @@ func BuildReportString(reportData *outputtypes.ReportData, config settings.Confi return reportStr } -func CalculateSeverity(groups []string, severity string, hasLocalDataTypes bool) string { +func CalculateSeverity(groups []string, severity string, hasLocalDataTypes bool) types.SeverityMeta { if severity == globaltypes.LevelWarning { - return globaltypes.LevelWarning + return types.SeverityMeta{ + RuleSeverity: severity, + DisplaySeverity: globaltypes.LevelWarning, + } } // highest sensitive data category @@ -423,16 +428,28 @@ func CalculateSeverity(groups []string, severity string, hasLocalDataTypes bool) triggerWeighting = 2 } - switch finalWeighting := ruleSeverityWeighting + (sensitiveDataCategoryWeighting * triggerWeighting); { + var displaySeverity string + finalWeighting := ruleSeverityWeighting + (sensitiveDataCategoryWeighting * triggerWeighting) + switch { case finalWeighting >= 8: - return globaltypes.LevelCritical + displaySeverity = globaltypes.LevelCritical case finalWeighting >= 5: - return globaltypes.LevelHigh + displaySeverity = globaltypes.LevelHigh case finalWeighting >= 3: - return globaltypes.LevelMedium + displaySeverity = globaltypes.LevelMedium + default: + displaySeverity = globaltypes.LevelLow } - return globaltypes.LevelLow + return types.SeverityMeta{ + RuleSeverity: severity, + SensitiveDataCategories: groups, + HasLocalDataTypes: &hasLocalDataTypes, + RuleSeverityWeighting: ruleSeverityWeighting, + SensitiveDataCategoryWeighting: sensitiveDataCategoryWeighting, + FinalWeighting: finalWeighting, + DisplaySeverity: displaySeverity, + } } func writeStatsToString( diff --git a/pkg/report/output/security/security_test.go b/pkg/report/output/security/security_test.go index 018e19f81..99a1a7500 100644 --- a/pkg/report/output/security/security_test.go +++ b/pkg/report/output/security/security_test.go @@ -13,6 +13,7 @@ import ( dataflowtypes "github.com/bearer/bearer/pkg/report/output/dataflow/types" "github.com/bearer/bearer/pkg/report/output/security" + securitytypes "github.com/bearer/bearer/pkg/report/output/security/types" "github.com/bearer/bearer/pkg/report/output/types" outputtypes "github.com/bearer/bearer/pkg/report/output/types" ) @@ -161,7 +162,7 @@ func TestAddReportDataWithSeverity(t *testing.T) { } func TestCalculateSeverity(t *testing.T) { - res := []string{ + res := []securitytypes.SeverityMeta{ security.CalculateSeverity([]string{"PHI", "Personal Data"}, "low", true), security.CalculateSeverity([]string{"Personal Data (Sensitive)"}, "low", false), security.CalculateSeverity([]string{"Personal Data"}, "low", false), diff --git a/pkg/report/output/security/types/types.go b/pkg/report/output/security/types/types.go index 8e6db1838..2540e9480 100644 --- a/pkg/report/output/security/types/types.go +++ b/pkg/report/output/security/types/types.go @@ -10,21 +10,21 @@ import ( type Finding struct { *Rule - LineNumber int `json:"line_number,omitempty" yaml:"line_number,omitempty"` - FullFilename string `json:"full_filename,omitempty" yaml:"full_filename,omitempty"` - Filename string `json:"filename,omitempty" yaml:"filename,omitempty"` - DataType *DataType `json:"data_type,omitempty" yaml:"data_type,omitempty"` - CategoryGroups []string `json:"category_groups,omitempty" yaml:"category_groups,omitempty"` - Source Source `json:"source,omitempty" yaml:"source,omitempty"` - Sink Sink `json:"sink,omitempty" yaml:"sink,omitempty"` - ParentLineNumber int `json:"parent_line_number,omitempty" yaml:"parent_line_number,omitempty"` - ParentContent string `json:"snippet,omitempty" yaml:"snippet,omitempty"` - Fingerprint string `json:"fingerprint,omitempty" yaml:"fingerprint,omitempty"` - OldFingerprint string `json:"old_fingerprint,omitempty" yaml:"old_fingerprint,omitempty"` - DetailedContext string `json:"detailed_context,omitempty" yaml:"detailed_context,omitempty"` - CodeExtract string `json:"code_extract,omitempty" yaml:"code_extract,omitempty"` - RawCodeExtract []file.Line `json:"-" yaml:"-"` - SomeExtraField string `json:"-" yaml:"-"` + LineNumber int `json:"line_number,omitempty" yaml:"line_number,omitempty"` + FullFilename string `json:"full_filename,omitempty" yaml:"full_filename,omitempty"` + Filename string `json:"filename,omitempty" yaml:"filename,omitempty"` + DataType *DataType `json:"data_type,omitempty" yaml:"data_type,omitempty"` + CategoryGroups []string `json:"category_groups,omitempty" yaml:"category_groups,omitempty"` + Source Source `json:"source,omitempty" yaml:"source,omitempty"` + Sink Sink `json:"sink,omitempty" yaml:"sink,omitempty"` + ParentLineNumber int `json:"parent_line_number,omitempty" yaml:"parent_line_number,omitempty"` + ParentContent string `json:"snippet,omitempty" yaml:"snippet,omitempty"` + Fingerprint string `json:"fingerprint,omitempty" yaml:"fingerprint,omitempty"` + OldFingerprint string `json:"old_fingerprint,omitempty" yaml:"old_fingerprint,omitempty"` + DetailedContext string `json:"detailed_context,omitempty" yaml:"detailed_context,omitempty"` + CodeExtract string `json:"code_extract,omitempty" yaml:"code_extract,omitempty"` + RawCodeExtract []file.Line `json:"-" yaml:"-"` + SeverityMeta SeverityMeta `json:"-" yaml:"-"` } type DataType struct { @@ -60,6 +60,16 @@ type Sink struct { Content string `json:"content" yaml:"content"` } +type SeverityMeta struct { + RuleSeverity string `json:"rule_severity" yaml:"rule_severity"` + SensitiveDataCategories []string `json:"sensitive_data_categories" yaml:"sensitive_data_categories"` + HasLocalDataTypes *bool `json:"local_data_types,omitempty" yaml:"local_data_types,omitempty"` + SensitiveDataCategoryWeighting int `json:"sensitive_data_category_weighting,omitempty" yaml:"sensitive_data_category_weighting,omitempty"` + RuleSeverityWeighting int `json:"rule_severity_weighting,omitempty" yaml:"rule_severity_weighting,omitempty"` + FinalWeighting int `json:"final_weighting,omitempty" yaml:"final_weighting,omitempty"` + DisplaySeverity string `json:"display_severity" yaml:"display_severity"` +} + func (f Finding) HighlightCodeExtract() string { result := "" for _, line := range f.RawCodeExtract {