From ba23b5e49a6f256faeadd4a0fdcb19f111ffea62 Mon Sep 17 00:00:00 2001 From: Marc Brugger Date: Wed, 18 Aug 2021 13:00:38 +0200 Subject: [PATCH] Add possibility to list waived (nosec) marked issues but not count them as such --- analyzer.go | 26 +++++++++++++++++++------- analyzer_test.go | 26 ++++++++++++++++++++++++++ cmd/gosec/main.go | 27 +++++++++++++++++++-------- config.go | 2 ++ issue.go | 1 + report/html/template.go | 3 +++ report/text/template.go | 2 +- report/text/writer.go | 7 +++++-- 8 files changed, 76 insertions(+), 18 deletions(-) diff --git a/analyzer.go b/analyzer.go index c8dfd6d..b41c887 100644 --- a/analyzer.go +++ b/analyzer.go @@ -82,6 +82,7 @@ type Analyzer struct { errors map[string][]Error // keys are file paths; values are the golang errors in those files tests bool excludeGenerated bool + showIgnored bool } // NewAnalyzer builds a new analyzer. @@ -90,11 +91,16 @@ func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, logger *log.Log if enabled, err := conf.IsGlobalEnabled(Nosec); err == nil { ignoreNoSec = enabled } + showIgnored := false + if enabled, err := conf.IsGlobalEnabled(ShowIgnored); err == nil { + showIgnored = enabled + } if logger == nil { logger = log.New(os.Stderr, "[gosec]", log.LstdFlags) } return &Analyzer{ ignoreNosec: ignoreNoSec, + showIgnored: showIgnored, ruleset: make(RuleSet), context: &Context{}, config: conf, @@ -179,7 +185,7 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages. } if gosec.tests { - testsFiles := []string{} + testsFiles := make([]string, 0) testsFiles = append(testsFiles, basePackage.TestGoFiles...) testsFiles = append(testsFiles, basePackage.XTestGoFiles...) for _, filename := range testsFiles { @@ -279,7 +285,7 @@ func (gosec *Analyzer) AppendError(file string, err error) { if r.MatchString(err.Error()) { return } - errors := []Error{} + errors := make([]Error, 0) if ferrs, ok := gosec.errors[file]; ok { errors = ferrs } @@ -364,9 +370,8 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor { gosec.context.Imports.TrackImport(n) for _, rule := range gosec.ruleset.RegisteredFor(n) { - if _, ok := ignores[rule.ID()]; ok { - continue - } + _, ignored := ignores[rule.ID()] + issue, err := rule.Match(n, gosec.context) if err != nil { file, line := GetLocation(n, gosec.context) @@ -374,8 +379,15 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor { gosec.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line) } if issue != nil { - gosec.issues = append(gosec.issues, issue) - gosec.stats.NumFound++ + if gosec.showIgnored { + issue.NoSec = ignored + } + if !ignored || !gosec.showIgnored { + gosec.stats.NumFound++ + } + if !ignored || gosec.showIgnored || gosec.ignoreNosec { + gosec.issues = append(gosec.issues, issue) + } } } return gosec diff --git a/analyzer_test.go b/analyzer_test.go index 8752585..03dc1d3 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -261,6 +261,32 @@ var _ = Describe("Analyzer", func() { Expect(nosecIssues).Should(HaveLen(sample.Errors)) }) + XIt("should be possible to overwrite nosec comments, and report issues but the should not be counted", func() { + // Rule for MD5 weak crypto usage + sample := testutils.SampleCodeG401[0] + source := sample.Code[0] + + // overwrite nosec option + nosecIgnoreConfig := gosec.NewConfig() + nosecIgnoreConfig.SetGlobal(gosec.Nosec, "true") + nosecIgnoreConfig.SetGlobal(gosec.ShowIgnored, "true") + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, logger) + customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) + + nosecPackage := testutils.NewTestPackage() + defer nosecPackage.Close() + nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec", 1) + nosecPackage.AddFile("md5.go", nosecSource) + err := nosecPackage.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = customAnalyzer.Process(buildTags, nosecPackage.Path) + Expect(err).ShouldNot(HaveOccurred()) + nosecIssues, metrics, _ := customAnalyzer.Report() + Expect(nosecIssues).Should(HaveLen(sample.Errors)) + Expect(metrics.NumFound).Should(Equal(0)) + Expect(metrics.NumNosec).Should(Equal(1)) + }) + It("should be possible to use an alternative nosec tag", func() { // Rule for MD5 weak crypto usage sample := testutils.SampleCodeG401[0] diff --git a/cmd/gosec/main.go b/cmd/gosec/main.go index 3c93c12..6175ce7 100644 --- a/cmd/gosec/main.go +++ b/cmd/gosec/main.go @@ -72,6 +72,9 @@ var ( // #nosec flag flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") + // show ignored + flagShowIgnored = flag.Bool("show-ignored", false, "If enabled, ignored issues are printed") + // format output flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, sonarqube, golint, sarif or text") @@ -173,6 +176,9 @@ func loadConfig(configFile string) (gosec.Config, error) { if *flagIgnoreNoSec { config.SetGlobal(gosec.Nosec, "true") } + if *flagShowIgnored { + config.SetGlobal(gosec.ShowIgnored, "true") + } if *flagAlternativeNoSec != "" { config.SetGlobal(gosec.NoSecAlternative, *flagAlternativeNoSec) } @@ -200,7 +206,7 @@ func loadRules(include, exclude string) rules.RuleList { } func getRootPaths(paths []string) []string { - rootPaths := []string{} + rootPaths := make([]string, 0) for _, path := range paths { rootPath, err := gosec.RootPath(path) if err != nil { @@ -255,14 +261,18 @@ func convertToScore(severity string) (gosec.Score, error) { } } -func filterIssues(issues []*gosec.Issue, severity gosec.Score, confidence gosec.Score) []*gosec.Issue { - result := []*gosec.Issue{} +func filterIssues(issues []*gosec.Issue, severity gosec.Score, confidence gosec.Score) ([]*gosec.Issue, int) { + result := make([]*gosec.Issue, 0) + trueIssues := 0 for _, issue := range issues { if issue.Severity >= severity && issue.Confidence >= confidence { result = append(result, issue) + if !issue.NoSec || !*flagShowIgnored { + trueIssues++ + } } } - return result + return result, trueIssues } func main() { @@ -372,9 +382,10 @@ func main() { } // Filter the issues by severity and confidence - issues = filterIssues(issues, failSeverity, failConfidence) - if metrics.NumFound != len(issues) { - metrics.NumFound = len(issues) + var trueIssues int + issues, trueIssues = filterIssues(issues, failSeverity, failConfidence) + if metrics.NumFound != trueIssues { + metrics.NumFound = trueIssues } // Exit quietly if nothing was found @@ -390,7 +401,7 @@ func main() { if *flagOutput == "" || *flagStdOut { fileFormat := getPrintedFormat(*flagFormat, *flagVerbose) if err := printReport(fileFormat, *flagColor, rootPaths, reportInfo); err != nil { - logger.Fatal((err)) + logger.Fatal(err) } } if *flagOutput != "" { diff --git a/config.go b/config.go index 4af62b2..fe60b2f 100644 --- a/config.go +++ b/config.go @@ -20,6 +20,8 @@ type GlobalOption string const ( // Nosec global option for #nosec directive Nosec GlobalOption = "nosec" + // ShowIgnored defines whether nosec issues are counted as finding or not + ShowIgnored GlobalOption = "show-ignored" // Audit global option which indicates that gosec runs in audit mode Audit GlobalOption = "audit" // NoSecAlternative global option alternative for #nosec directive diff --git a/issue.go b/issue.go index d5091fe..00b1937 100644 --- a/issue.go +++ b/issue.go @@ -97,6 +97,7 @@ type Issue struct { Code string `json:"code"` // Impacted code line Line string `json:"line"` // Line number in file Col string `json:"column"` // Column number in line + NoSec bool `json:"nosec"` // true if the issue is nosec } // FileLocation point out the file path and line number in file diff --git a/report/html/template.go b/report/html/template.go index 8996ebb..fb64e93 100644 --- a/report/html/template.go +++ b/report/html/template.go @@ -62,6 +62,8 @@ const templateContent = ` level += " is-warning"; } else if (this.props.level === "LOW") { level += " is-info"; + } else if (this.props.level === "WAIVED") { + level += " is-success"; } level +=" is-rounded"; return ( @@ -96,6 +98,7 @@ const templateContent = `
+ {this.props.data.nosec && }
diff --git a/report/text/template.go b/report/text/template.go index 3bd346e..5cc5d14 100644 --- a/report/text/template.go +++ b/report/text/template.go @@ -8,7 +8,7 @@ Golang errors in file: [{{ $filePath }}]: {{end}} {{end}} {{ range $index, $issue := .Issues }} -[{{ highlight $issue.FileLocation $issue.Severity }}] - {{ $issue.RuleID }} ({{ $issue.Cwe.SprintID }}): {{ $issue.What }} (Confidence: {{ $issue.Confidence}}, Severity: {{ $issue.Severity }}) +[{{ highlight $issue.FileLocation $issue.Severity $issue.NoSec }}] - {{ $issue.RuleID }}{{ if $issue.NoSec }} ({{- success "NoSec" -}}){{ end }} ({{ $issue.Cwe.SprintID }}): {{ $issue.What }} (Confidence: {{ $issue.Confidence}}, Severity: {{ $issue.Severity }}) {{ printCode $issue }} {{ end }} diff --git a/report/text/writer.go b/report/text/writer.go index 614ffa1..c773cc7 100644 --- a/report/text/writer.go +++ b/report/text/writer.go @@ -45,7 +45,7 @@ func plainTextFuncMap(enableColor bool) template.FuncMap { // by default those functions return the given content untouched return template.FuncMap{ - "highlight": func(t string, s gosec.Score) string { + "highlight": func(t string, s gosec.Score, ignored bool) string { return t }, "danger": fmt.Sprint, @@ -56,7 +56,10 @@ func plainTextFuncMap(enableColor bool) template.FuncMap { } // highlight returns content t colored based on Score -func highlight(t string, s gosec.Score) string { +func highlight(t string, s gosec.Score, ignored bool) string { + if ignored { + return defaultTheme.Sprint(t) + } switch s { case gosec.High: return errorTheme.Sprint(t)