From 929c118e26ece0aa420449a575249976933bc067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Fabianski?= Date: Tue, 14 Nov 2023 16:46:13 +0100 Subject: [PATCH 1/4] feat(report): add new jsonv2 format for security --- .tool-versions | 1 + ...rtFlagsShouldFail-invalid-format-flag-security | 4 ++-- internal/flag/report_flags.go | 5 +++-- internal/report/output/security/formatter.go | 13 +++++++++++++ internal/report/output/security/security.go | 7 +++++++ internal/report/output/security/types/types.go | 15 +++++++++++++++ internal/report/output/types/types.go | 1 + 7 files changed, 42 insertions(+), 4 deletions(-) create mode 100644 .tool-versions diff --git a/.tool-versions b/.tool-versions new file mode 100644 index 000000000..e4ab54ccd --- /dev/null +++ b/.tool-versions @@ -0,0 +1 @@ +golang 1.21.1 diff --git a/e2e/flags/.snapshots/TestReportFlagsShouldFail-invalid-format-flag-security b/e2e/flags/.snapshots/TestReportFlagsShouldFail-invalid-format-flag-security index b3a1ea636..7141b9e51 100644 --- a/e2e/flags/.snapshots/TestReportFlagsShouldFail-invalid-format-flag-security +++ b/e2e/flags/.snapshots/TestReportFlagsShouldFail-invalid-format-flag-security @@ -1,6 +1,6 @@ -- -Error: flag error: report flags error: invalid format argument for security report; supported values: json, yaml, sarif, gitlab-sast, rdjson, html +Error: flag error: report flags error: invalid format argument for security report; supported values: json, yaml, sarif, gitlab-sast, rdjson, html, jsonv2 Usage: bearer scan [flags] Aliases: @@ -46,5 +46,5 @@ General Flags --no-color Disable color in output -flag error: report flags error: invalid format argument for security report; supported values: json, yaml, sarif, gitlab-sast, rdjson, html +flag error: report flags error: invalid format argument for security report; supported values: json, yaml, sarif, gitlab-sast, rdjson, html, jsonv2 diff --git a/internal/flag/report_flags.go b/internal/flag/report_flags.go index 9a1007a9c..3b0236f8b 100644 --- a/internal/flag/report_flags.go +++ b/internal/flag/report_flags.go @@ -14,6 +14,7 @@ var ( FormatGitLabSast = "gitlab-sast" FormatSarif = "sarif" FormatJSON = "json" + FormatJSONV2 = "jsonv2" FormatYAML = "yaml" FormatHTML = "html" FormatCSV = "csv" @@ -28,7 +29,7 @@ var ( ) var ( - ErrInvalidFormatSecurity = errors.New("invalid format argument for security report; supported values: json, yaml, sarif, gitlab-sast, rdjson, html") + ErrInvalidFormatSecurity = errors.New("invalid format argument for security report; supported values: json, yaml, sarif, gitlab-sast, rdjson, html, jsonv2") ErrInvalidFormatPrivacy = errors.New("invalid format argument for privacy report; supported values: csv, json, yaml, html") ErrInvalidFormatDefault = errors.New("invalid format argument; supported values: json, yaml") ErrInvalidReport = errors.New("invalid report argument; supported values: security, privacy") @@ -153,7 +154,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { if report != ReportPrivacy { return ReportOptions{}, invalidFormat } - case FormatSarif, FormatGitLabSast, FormatReviewDog: + case FormatSarif, FormatGitLabSast, FormatReviewDog, FormatJSONV2: if report != ReportSecurity { return ReportOptions{}, invalidFormat } diff --git a/internal/report/output/security/formatter.go b/internal/report/output/security/formatter.go index 96d28fc89..7a965011b 100644 --- a/internal/report/output/security/formatter.go +++ b/internal/report/output/security/formatter.go @@ -6,6 +6,7 @@ import ( "github.com/hhatto/gocloc" + "github.com/bearer/bearer/cmd/bearer/build" "github.com/bearer/bearer/internal/commands/process/settings" "github.com/bearer/bearer/internal/flag" "github.com/bearer/bearer/internal/report/output/gitlab" @@ -24,6 +25,12 @@ type Formatter struct { EndTime time.Time } +type RawFindingsOutput struct { + Source string `json:"source" yaml:"source"` + Version string `json:"version" yaml:"version"` + Findings RawFindings `json:"findings" yaml:"findings"` +} + func NewFormatter(reportData *outputtypes.ReportData, config settings.Config, goclocResult *gocloc.Result, startTime time.Time, endTime time.Time) *Formatter { return &Formatter{ ReportData: reportData, @@ -58,6 +65,12 @@ func (f Formatter) Format(format string) (output string, err error) { return outputhandler.ReportJSON(sastContent) case flag.FormatJSON: return outputhandler.ReportJSON(f.ReportData.FindingsBySeverity) + case flag.FormatJSONV2: + return outputhandler.ReportJSON(RawFindingsOutput{ + Source: "Bearer", + Version: build.Version, + Findings: f.ReportData.RawFindings, + }) case flag.FormatYAML: return outputhandler.ReportYAML(f.ReportData.FindingsBySeverity) case flag.FormatHTML: diff --git a/internal/report/output/security/security.go b/internal/report/output/security/security.go index 0d04c310d..3e7c66ea0 100644 --- a/internal/report/output/security/security.go +++ b/internal/report/output/security/security.go @@ -42,6 +42,7 @@ var severityColorFns = map[string]func(x ...interface{}) string{ globaltypes.LevelWarning: color.New(color.FgCyan).SprintFunc(), } +type RawFindings = []types.RawFinding type Findings = map[string][]types.Finding type IgnoredFindings = map[string][]types.IgnoredFinding @@ -99,6 +100,12 @@ func AddReportData( return err } + for severity, findingsSlice := range summaryFindings { + for _, finding := range findingsSlice { + reportData.RawFindings = append(reportData.RawFindings, finding.ToRawFinding(severity)) + } + } + if !config.Scan.Quiet { fingerprintOutput( append(fingerprints, builtInFingerprints...), diff --git a/internal/report/output/security/types/types.go b/internal/report/output/security/types/types.go index 45888190e..5aafcc4ee 100644 --- a/internal/report/output/security/types/types.go +++ b/internal/report/output/security/types/types.go @@ -1,6 +1,7 @@ package types import ( + "encoding/json" "fmt" "strings" @@ -10,6 +11,11 @@ import ( ignoretypes "github.com/bearer/bearer/internal/util/ignore/types" ) +type RawFinding struct { + *Finding + Severity string `json:"severity" yaml:"severity"` +} + type Finding struct { *Rule LineNumber int `json:"line_number,omitempty" yaml:"line_number,omitempty"` @@ -36,9 +42,18 @@ type IgnoredFinding struct { type GenericFinding interface { GetFinding() Finding + ToRawFinding(severity string) RawFinding GetIgnoreMeta() *ignoretypes.IgnoredFingerprint } +func (f Finding) ToRawFinding(severity string) RawFinding { + rawFindingJson, _ := json.Marshal(f) + var rawFinding RawFinding + json.Unmarshal(rawFindingJson, &rawFinding) + rawFinding.Severity = f.SeverityMeta.DisplaySeverity + return rawFinding +} + func (f Finding) GetFinding() Finding { return f } diff --git a/internal/report/output/types/types.go b/internal/report/output/types/types.go index 4a4635cd8..94f457a50 100644 --- a/internal/report/output/types/types.go +++ b/internal/report/output/types/types.go @@ -14,6 +14,7 @@ type ReportData struct { FoundLanguages map[string]int32 // language => loc e.g. { "Ruby": 6742, "JavaScript": 122 } Detectors []any Dataflow *DataFlow + RawFindings []securitytypes.RawFinding `json:"findings"` FindingsBySeverity map[string][]securitytypes.Finding IgnoredFindingsBySeverity map[string][]securitytypes.IgnoredFinding PrivacyReport *privacytypes.Report From 100a9659fd58f7830c042026d9671e3586b5a436 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Fabianski?= Date: Wed, 15 Nov 2023 17:00:15 +0100 Subject: [PATCH 2/4] chore: add tests for jsonv2 --- e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 | 5 +++++ e2e/flags/report_flags_test.go | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 diff --git a/e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 b/e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 new file mode 100644 index 000000000..be21b2006 --- /dev/null +++ b/e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 @@ -0,0 +1,5 @@ +{"source":"Bearer","version":"dev","findings":null} + +-- +Analyzing codebase + diff --git a/e2e/flags/report_flags_test.go b/e2e/flags/report_flags_test.go index b8b2d42a3..f47542547 100644 --- a/e2e/flags/report_flags_test.go +++ b/e2e/flags/report_flags_test.go @@ -29,6 +29,14 @@ func TestReportFlags(t *testing.T) { testhelper.RunTests(t, tests) } +func TestReportJsonV2Flags(t *testing.T) { + tests := []testhelper.TestCase{ + newScanTest("format-jsonv2", []string{"--format=jsonv2", "--external-rule-dir=e2e/testdata/rules"}), + } + + testhelper.RunTests(t, tests) +} + func TestReportFlagsShouldFail(t *testing.T) { t.Parallel() tests := []testhelper.TestCase{ @@ -43,7 +51,6 @@ func TestReportFlagsShouldFail(t *testing.T) { } testhelper.RunTests(t, tests) - } func TestOuputFlag(t *testing.T) { From 10018b6f192caedf4e09fafa355e9ba6a3fb76a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Fabianski?= Date: Wed, 15 Nov 2023 17:04:17 +0100 Subject: [PATCH 3/4] chore: appease linter --- internal/report/output/security/types/types.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/internal/report/output/security/types/types.go b/internal/report/output/security/types/types.go index 5aafcc4ee..fbcc87212 100644 --- a/internal/report/output/security/types/types.go +++ b/internal/report/output/security/types/types.go @@ -49,7 +49,11 @@ type GenericFinding interface { func (f Finding) ToRawFinding(severity string) RawFinding { rawFindingJson, _ := json.Marshal(f) var rawFinding RawFinding - json.Unmarshal(rawFindingJson, &rawFinding) + err := json.Unmarshal(rawFindingJson, &rawFinding) + if err != nil { + return RawFinding{} + } + rawFinding.Severity = f.SeverityMeta.DisplaySeverity return rawFinding } From 37eeba02619a1624ae979aee3ba9d3dae016a4b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Fabianski?= Date: Wed, 15 Nov 2023 17:46:17 +0100 Subject: [PATCH 4/4] chore: update snapshots --- .../.snapshots/TestReportFlagsShouldFail-format-jsonv2 | 5 +++++ e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 | 5 ----- e2e/flags/report_flags_test.go | 9 +-------- 3 files changed, 6 insertions(+), 13 deletions(-) create mode 100644 e2e/flags/.snapshots/TestReportFlagsShouldFail-format-jsonv2 delete mode 100644 e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 diff --git a/e2e/flags/.snapshots/TestReportFlagsShouldFail-format-jsonv2 b/e2e/flags/.snapshots/TestReportFlagsShouldFail-format-jsonv2 new file mode 100644 index 000000000..ad1c7b2d4 --- /dev/null +++ b/e2e/flags/.snapshots/TestReportFlagsShouldFail-format-jsonv2 @@ -0,0 +1,5 @@ +{"source":"Bearer","version":"dev","findings":[{"cwe_ids":["42"],"id":"test_ruby_logger","title":"Ruby logger","description":"Ruby logger","documentation_url":"","line_number":1,"full_filename":"e2e/flags/testdata/simple/main.rb","filename":"main.rb","data_type":{"category_uuid":"cef587dd-76db-430b-9e18-7b031e1a193b","name":"Email Address"},"category_groups":["PII","Personal Data"],"source":{"start":1,"end":1,"column":{"start":26,"end":36}},"sink":{"start":1,"end":1,"column":{"start":1,"end":37},"content":"logger.info(\"user info\", user.email)"},"parent_line_number":1,"snippet":"logger.info(\"user info\", user.email)","fingerprint":"fa5e03644738e4c17cbbd04a580506b1_0","old_fingerprint":"8240e1537878783bac845d1163c80555_0","code_extract":"logger.info(\"user info\", user.email)","severity":"critical"}]} + +-- +Analyzing codebase + diff --git a/e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 b/e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 deleted file mode 100644 index be21b2006..000000000 --- a/e2e/flags/.snapshots/TestReportJsonV2Flags-format-jsonv2 +++ /dev/null @@ -1,5 +0,0 @@ -{"source":"Bearer","version":"dev","findings":null} - --- -Analyzing codebase - diff --git a/e2e/flags/report_flags_test.go b/e2e/flags/report_flags_test.go index f47542547..1f1a7f9ec 100644 --- a/e2e/flags/report_flags_test.go +++ b/e2e/flags/report_flags_test.go @@ -29,14 +29,6 @@ func TestReportFlags(t *testing.T) { testhelper.RunTests(t, tests) } -func TestReportJsonV2Flags(t *testing.T) { - tests := []testhelper.TestCase{ - newScanTest("format-jsonv2", []string{"--format=jsonv2", "--external-rule-dir=e2e/testdata/rules"}), - } - - testhelper.RunTests(t, tests) -} - func TestReportFlagsShouldFail(t *testing.T) { t.Parallel() tests := []testhelper.TestCase{ @@ -44,6 +36,7 @@ func TestReportFlagsShouldFail(t *testing.T) { newScanTest("invalid-format-flag-security", []string{"--format=testing"}), newScanTest("invalid-format-flag-privacy", []string{"--report=privacy", "--format=testing"}), newScanTest("invalid-context-flag", []string{"--context=testing"}), + newScanTest("format-jsonv2", []string{"--format=jsonv2", "--external-rule-dir=e2e/testdata/rules"}), } for i := range tests {