From 79fbf3af8d832ff2d2e098055c4b366bad172bce Mon Sep 17 00:00:00 2001 From: Hiroki Suezawa Date: Fri, 3 Jan 2020 18:56:21 +0900 Subject: [PATCH] Add golint format to output format (#428) Signed-off-by: Hiroki Suezawa --- README.md | 2 +- cmd/gosec/main.go | 2 +- issue.go | 4 ++++ issue_test.go | 2 ++ output/formatter.go | 32 ++++++++++++++++++++++++++++++++ output/formatter_test.go | 14 ++++++++++++++ 6 files changed, 54 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 91652b7..ca4eb15 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ gosec -tag debug,ignore ./... ### Output formats -gosec currently supports text, json, yaml, csv, sonarqube and JUnit XML output formats. By default +gosec currently supports text, json, yaml, csv, sonarqube, JUnit XML and golint output formats. By default results will be reported to stdout, but can also be written to an output file. The output format is controlled by the '-fmt' flag, and the output file is controlled by the '-out' flag as follows: diff --git a/cmd/gosec/main.go b/cmd/gosec/main.go index 3a4e7d5..236c499 100644 --- a/cmd/gosec/main.go +++ b/cmd/gosec/main.go @@ -73,7 +73,7 @@ var ( flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") // format output - flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, sonarqube, or text") + flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, sonarqube, golint or text") // #nosec alternative tag flagAlternativeNoSec = flag.String("nosec-tag", "", "Set an alternative string for #nosec. Some examples: #dontanalyze, #falsepositive") diff --git a/issue.go b/issue.go index d54d981..297030c 100644 --- a/issue.go +++ b/issue.go @@ -83,6 +83,7 @@ type Issue struct { File string `json:"file"` // File name we found it in Code string `json:"code"` // Impacted code line Line string `json:"line"` // Line number in file + Col string `json:"column"` // Column number in line } // MetaData is embedded in all gosec rules. The Severity, Confidence and What message @@ -142,6 +143,8 @@ func NewIssue(ctx *Context, node ast.Node, ruleID, desc string, severity Score, line = fmt.Sprintf("%d-%d", start, end) } + col := strconv.Itoa(fobj.Position(node.Pos()).Column) + // #nosec if file, err := os.Open(fobj.Name()); err == nil { defer file.Close() @@ -156,6 +159,7 @@ func NewIssue(ctx *Context, node ast.Node, ruleID, desc string, severity Score, return &Issue{ File: name, Line: line, + Col: col, RuleID: ruleID, What: desc, Confidence: confidence, diff --git a/issue_test.go b/issue_test.go index 25a0ac2..ebf2464 100644 --- a/issue_test.go +++ b/issue_test.go @@ -41,6 +41,7 @@ var _ = Describe("Issue", func() { Expect(issue).ShouldNot(BeNil()) Expect(issue.Code).Should(MatchRegexp(`"bar"`)) Expect(issue.Line).Should(Equal("2")) + Expect(issue.Col).Should(Equal("16")) Expect(issue.Cwe.ID).Should(Equal("")) }) @@ -84,6 +85,7 @@ var _ = Describe("Issue", func() { Expect(issue).ShouldNot(BeNil()) Expect(issue.File).Should(MatchRegexp("foo.go")) Expect(issue.Line).Should(MatchRegexp("3-4")) + Expect(issue.Col).Should(Equal("21")) }) It("should maintain the provided severity score", func() { diff --git a/output/formatter.go b/output/formatter.go index 6b6a092..0aca5ea 100644 --- a/output/formatter.go +++ b/output/formatter.go @@ -99,6 +99,8 @@ func CreateReport(w io.Writer, format string, rootPaths []string, issues []*gose err = reportFromPlaintextTemplate(w, text, data) case "sonarqube": err = reportSonarqube(rootPaths, w, data) + case "golint": + err = reportGolint(w, data) default: err = reportFromPlaintextTemplate(w, text, data) } @@ -202,6 +204,36 @@ func reportCSV(w io.Writer, data *reportInfo) error { return nil } +func reportGolint(w io.Writer, data *reportInfo) error { + // Output Sample: + // /tmp/main.go:11:14: [CWE-310] RSA keys should be at least 2048 bits (Rule:G403, Severity:MEDIUM, Confidence:HIGH) + + for _, issue := range data.Issues { + what := issue.What + if issue.Cwe.ID != "" { + what = fmt.Sprintf("[CWE-%s] %s", issue.Cwe.ID, issue.What) + } + + // issue.Line uses "start-end" format for multiple line detection. + lines := strings.Split(issue.Line, "-") + start := lines[0] + + _, err := fmt.Fprintf(w, "%s:%s:%s: %s (Rule:%s, Severity:%s, Confidence:%s)\n", + issue.File, + start, + issue.Col, + what, + issue.RuleID, + issue.Severity.String(), + issue.Confidence.String(), + ) + if err != nil { + return err + } + } + return nil +} + func reportJUnitXML(w io.Writer, data *reportInfo) error { groupedData := groupDataByRules(data) junitXMLStruct := createJUnitXMLStruct(groupedData) diff --git a/output/formatter_test.go b/output/formatter_test.go index 520ce36..6aeb64a 100644 --- a/output/formatter_test.go +++ b/output/formatter_test.go @@ -15,6 +15,7 @@ func createIssue(ruleID string, cwe gosec.Cwe) gosec.Issue { return gosec.Issue{ File: "/home/src/project/test.go", Line: "1", + Col: "1", RuleID: ruleID, What: "test", Confidence: gosec.High, @@ -391,5 +392,18 @@ var _ = Describe("Formatter", func() { Expect(result).To(ContainSubstring(expectation)) } }) + It("golint formatted report should contain the CWE mapping", func() { + for _, rule := range grules { + cwe := gosec.IssueToCWE[rule] + issue := createIssue(rule, cwe) + error := map[string][]gosec.Error{} + + buf := new(bytes.Buffer) + CreateReport(buf, "golint", []string{}, []*gosec.Issue{&issue}, &gosec.Metrics{}, error) + 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(string(buf.Bytes())).To(Equal(expect)) + } + }) }) })