package report import ( "bytes" "encoding/json" "fmt" "strings" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/securego/gosec/v2" "github.com/securego/gosec/v2/cwe" "github.com/securego/gosec/v2/issue" "github.com/securego/gosec/v2/report/junit" "github.com/securego/gosec/v2/report/sonar" "gopkg.in/yaml.v3" ) func createIssueWithFileWhat(file, what string) *issue.Issue { issue := createIssue("i1", issue.GetCweByRule("G101")) issue.File = file issue.What = what return &issue } func createIssue(ruleID string, weakness *cwe.Weakness) issue.Issue { return issue.Issue{ File: "/home/src/project/test.go", Line: "1", Col: "1", RuleID: ruleID, What: "test", Confidence: issue.High, Severity: issue.High, Code: "1: testcode", Cwe: weakness, } } func createReportInfo(rule string, weakness *cwe.Weakness) gosec.ReportInfo { newissue := createIssue(rule, weakness) metrics := gosec.Metrics{} return gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*issue.Issue{ &newissue, }, Stats: &metrics, } } func stripString(str string) string { ret := strings.Replace(str, "\n", "", -1) ret = strings.Replace(ret, " ", "", -1) ret = strings.Replace(ret, "\t", "", -1) return ret } var _ = Describe("Formatter", func() { BeforeEach(func() { }) Context("when converting to Sonarqube issues", func() { It("it should parse the report info", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, RuleID: "test", What: "test", File: "/home/src/project/test.go", Code: "", Line: "1-2", }, }, Stats: &gosec.Metrics{ NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0, }, } want := &sonar.Report{ Issues: []*sonar.Issue{ { EngineID: "gosec", RuleID: "test", PrimaryLocation: &sonar.Location{ Message: "test", FilePath: "test.go", TextRange: &sonar.TextRange{ StartLine: 1, EndLine: 2, }, }, Type: "VULNERABILITY", Severity: "BLOCKER", EffortMinutes: sonar.EffortMinutes, }, }, } rootPath := "/home/src/project" issues, err := sonar.GenerateReport([]string{rootPath}, data) Expect(err).ShouldNot(HaveOccurred()) Expect(*issues).To(Equal(*want)) }) It("it should parse the report info with files in subfolders", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, RuleID: "test", What: "test", File: "/home/src/project/subfolder/test.go", Code: "", Line: "1-2", }, }, Stats: &gosec.Metrics{ NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0, }, } want := &sonar.Report{ Issues: []*sonar.Issue{ { EngineID: "gosec", RuleID: "test", PrimaryLocation: &sonar.Location{ Message: "test", FilePath: "subfolder/test.go", TextRange: &sonar.TextRange{ StartLine: 1, EndLine: 2, }, }, Type: "VULNERABILITY", Severity: "BLOCKER", EffortMinutes: sonar.EffortMinutes, }, }, } rootPath := "/home/src/project" issues, err := sonar.GenerateReport([]string{rootPath}, data) Expect(err).ShouldNot(HaveOccurred()) Expect(*issues).To(Equal(*want)) }) It("it should not parse the report info for files from other projects", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, RuleID: "test", What: "test", File: "/home/src/project1/test.go", Code: "", Line: "1-2", }, }, Stats: &gosec.Metrics{ NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0, }, } want := &sonar.Report{ Issues: []*sonar.Issue{}, } rootPath := "/home/src/project2" issues, err := sonar.GenerateReport([]string{rootPath}, data) Expect(err).ShouldNot(HaveOccurred()) Expect(*issues).To(Equal(*want)) }) It("it should parse the report info for multiple projects projects", func() { data := &gosec.ReportInfo{ Errors: map[string][]gosec.Error{}, Issues: []*issue.Issue{ { Severity: 2, Confidence: 0, RuleID: "test", What: "test", File: "/home/src/project1/test-project1.go", Code: "", Line: "1-2", }, { Severity: 2, Confidence: 0, RuleID: "test", What: "test", File: "/home/src/project2/test-project2.go", Code: "", Line: "1-2", }, }, Stats: &gosec.Metrics{ NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0, }, } want := &sonar.Report{ Issues: []*sonar.Issue{ { EngineID: "gosec", RuleID: "test", PrimaryLocation: &sonar.Location{ Message: "test", FilePath: "test-project1.go", TextRange: &sonar.TextRange{ StartLine: 1, EndLine: 2, }, }, Type: "VULNERABILITY", Severity: "BLOCKER", EffortMinutes: sonar.EffortMinutes, }, { EngineID: "gosec", RuleID: "test", PrimaryLocation: &sonar.Location{ Message: "test", FilePath: "test-project2.go", TextRange: &sonar.TextRange{ StartLine: 1, EndLine: 2, }, }, Type: "VULNERABILITY", Severity: "BLOCKER", EffortMinutes: sonar.EffortMinutes, }, }, } rootPaths := []string{"/home/src/project1", "/home/src/project2"} issues, err := sonar.GenerateReport(rootPaths, data) Expect(err).ShouldNot(HaveOccurred()) Expect(*issues).To(Equal(*want)) }) }) Context("When using junit", func() { It("preserves order of issues", func() { issues := []*issue.Issue{createIssueWithFileWhat("i1", "1"), createIssueWithFileWhat("i2", "2"), createIssueWithFileWhat("i3", "1")} junitReport := junit.GenerateReport(&gosec.ReportInfo{Issues: issues}) testSuite := junitReport.Testsuites[0] Expect(testSuite.Testcases[0].Name).To(Equal(issues[0].File)) Expect(testSuite.Testcases[1].Name).To(Equal(issues[2].File)) testSuite = junitReport.Testsuites[1] Expect(testSuite.Testcases[0].Name).To(Equal(issues[1].File)) }) }) Context("When using different report formats", func() { grules := []string{ "G101", "G102", "G103", "G104", "G106", "G107", "G109", "G110", "G111", "G112", "G113", "G201", "G202", "G203", "G204", "G301", "G302", "G303", "G304", "G305", "G401", "G402", "G403", "G404", "G501", "G502", "G503", "G504", "G505", "G601", } It("csv formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err := CreateReport(buf, "csv", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) pattern := "/home/src/project/test.go,1,test,HIGH,HIGH,1: testcode,CWE-%s\n" expect := fmt.Sprintf(pattern, cwe.ID) Expect(buf.String()).To(Equal(expect)) } }) It("xml formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{NumFiles: 0, NumLines: 0, NumNosec: 0, NumFound: 0}, error).WithVersion("v2.7.0") err := CreateReport(buf, "xml", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) pattern := "Results:\n\n\n[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)\n > 1: testcode\n\n\n\nSummary:\n Gosec : v2.7.0\n Files : 0\n Lines : 0\n Nosec : 0\n Issues : 0\n\n" expect := fmt.Sprintf(pattern, rule, cwe.ID) Expect(buf.String()).To(Equal(expect)) } }) It("json formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) expect := new(bytes.Buffer) enc := json.NewEncoder(expect) err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "json", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) expectation := stripString(expect.String()) Expect(result).To(Equal(expectation)) } }) It("html formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) expect := new(bytes.Buffer) enc := json.NewEncoder(expect) err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "html", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) expectation := stripString(expect.String()) Expect(result).To(ContainSubstring(expectation)) } }) It("yaml formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) expect := new(bytes.Buffer) enc := yaml.NewEncoder(expect) err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "yaml", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) expectation := stripString(expect.String()) Expect(result).To(ContainSubstring(expectation)) } }) It("junit-xml formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) expect := new(bytes.Buffer) enc := yaml.NewEncoder(expect) err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "junit-xml", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) expectation := stripString(fmt.Sprintf("[/home/src/project/test.go:1] - test (Confidence: 2, Severity: 2, CWE: %s)", cwe.ID)) result := stripString(buf.String()) Expect(result).To(ContainSubstring(expectation)) } }) It("text formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} data := createReportInfo(rule, cwe) expect := new(bytes.Buffer) enc := yaml.NewEncoder(expect) err := enc.Encode(data) Expect(err).ShouldNot(HaveOccurred()) buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err = CreateReport(buf, "text", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) expectation := stripString(fmt.Sprintf("[/home/src/project/test.go:1] - %s (CWE-%s): test (Confidence: HIGH, Severity: HIGH)", rule, cwe.ID)) result := stripString(buf.String()) Expect(result).To(ContainSubstring(expectation)) } }) It("sonarqube formatted report shouldn't contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err := CreateReport(buf, "sonarqube", false, []string{"/home/src/project"}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) expect := new(bytes.Buffer) enc := json.NewEncoder(expect) err = enc.Encode(cwe) Expect(err).ShouldNot(HaveOccurred()) expectation := stripString(expect.String()) Expect(result).ShouldNot(ContainSubstring(expectation)) } }) It("golint formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error) err := CreateReport(buf, "golint", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) pattern := "/home/src/project/test.go:1:1: [CWE-%s] test (Rule:%s, Severity:HIGH, Confidence:HIGH)\n" expect := fmt.Sprintf(pattern, cwe.ID, rule) Expect(buf.String()).To(Equal(expect)) } }) It("sarif formatted report should contain the CWE mapping", func() { for _, rule := range grules { cwe := issue.GetCweByRule(rule) newissue := createIssue(rule, cwe) error := map[string][]gosec.Error{} buf := new(bytes.Buffer) reportInfo := gosec.NewReportInfo([]*issue.Issue{&newissue}, &gosec.Metrics{}, error).WithVersion("v2.7.0") err := CreateReport(buf, "sarif", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) ruleIDPattern := "\"id\":\"%s\"" expectedRule := fmt.Sprintf(ruleIDPattern, rule) Expect(err).ShouldNot(HaveOccurred()) Expect(result).To(ContainSubstring(expectedRule)) cweURIPattern := "\"helpUri\":\"https://cwe.mitre.org/data/definitions/%s.html\"" expectedCweURI := fmt.Sprintf(cweURIPattern, cwe.ID) Expect(err).ShouldNot(HaveOccurred()) Expect(result).To(ContainSubstring(expectedCweURI)) cweIDPattern := "\"id\":\"%s\"" expectedCweID := fmt.Sprintf(cweIDPattern, cwe.ID) Expect(err).ShouldNot(HaveOccurred()) Expect(result).To(ContainSubstring(expectedCweID)) } }) }) Context("When converting suppressed issues", func() { ruleID := "G101" cwe := issue.GetCweByRule(ruleID) suppressions := []issue.SuppressionInfo{ { Kind: "kind", Justification: "justification", }, } suppressedIssue := createIssue(ruleID, cwe) suppressedIssue.WithSuppressions(suppressions) It("text formatted report should contain the suppressed issues", func() { error := map[string][]gosec.Error{} reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, error) buf := new(bytes.Buffer) err := CreateReport(buf, "text", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) Expect(result).To(ContainSubstring("Results:Summary")) }) It("sarif formatted report should contain the suppressed issues", func() { error := map[string][]gosec.Error{} reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, error) buf := new(bytes.Buffer) err := CreateReport(buf, "sarif", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) Expect(result).To(ContainSubstring(`"results":[{`)) }) It("json formatted report should contain the suppressed issues", func() { error := map[string][]gosec.Error{} reportInfo := gosec.NewReportInfo([]*issue.Issue{&suppressedIssue}, &gosec.Metrics{}, error) buf := new(bytes.Buffer) err := CreateReport(buf, "json", false, []string{}, reportInfo) Expect(err).ShouldNot(HaveOccurred()) result := stripString(buf.String()) Expect(result).To(ContainSubstring(`"Issues":[{`)) }) }) })