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-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/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/e2e/flags/report_flags_test.go b/e2e/flags/report_flags_test.go index b8b2d42a3..1f1a7f9ec 100644 --- a/e2e/flags/report_flags_test.go +++ b/e2e/flags/report_flags_test.go @@ -36,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 { @@ -43,7 +44,6 @@ func TestReportFlagsShouldFail(t *testing.T) { } testhelper.RunTests(t, tests) - } func TestOuputFlag(t *testing.T) { 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..fbcc87212 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,22 @@ 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 + err := json.Unmarshal(rawFindingJson, &rawFinding) + if err != nil { + return 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