Add possibility to list waived (nosec) marked issues but not count them as such

This commit is contained in:
Marc Brugger 2021-08-18 13:00:38 +02:00 committed by GitHub
parent 5a131be2ec
commit ba23b5e49a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 76 additions and 18 deletions

View file

@ -82,6 +82,7 @@ type Analyzer struct {
errors map[string][]Error // keys are file paths; values are the golang errors in those files errors map[string][]Error // keys are file paths; values are the golang errors in those files
tests bool tests bool
excludeGenerated bool excludeGenerated bool
showIgnored bool
} }
// NewAnalyzer builds a new analyzer. // 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 { if enabled, err := conf.IsGlobalEnabled(Nosec); err == nil {
ignoreNoSec = enabled ignoreNoSec = enabled
} }
showIgnored := false
if enabled, err := conf.IsGlobalEnabled(ShowIgnored); err == nil {
showIgnored = enabled
}
if logger == nil { if logger == nil {
logger = log.New(os.Stderr, "[gosec]", log.LstdFlags) logger = log.New(os.Stderr, "[gosec]", log.LstdFlags)
} }
return &Analyzer{ return &Analyzer{
ignoreNosec: ignoreNoSec, ignoreNosec: ignoreNoSec,
showIgnored: showIgnored,
ruleset: make(RuleSet), ruleset: make(RuleSet),
context: &Context{}, context: &Context{},
config: conf, config: conf,
@ -179,7 +185,7 @@ func (gosec *Analyzer) load(pkgPath string, conf *packages.Config) ([]*packages.
} }
if gosec.tests { if gosec.tests {
testsFiles := []string{} testsFiles := make([]string, 0)
testsFiles = append(testsFiles, basePackage.TestGoFiles...) testsFiles = append(testsFiles, basePackage.TestGoFiles...)
testsFiles = append(testsFiles, basePackage.XTestGoFiles...) testsFiles = append(testsFiles, basePackage.XTestGoFiles...)
for _, filename := range testsFiles { for _, filename := range testsFiles {
@ -279,7 +285,7 @@ func (gosec *Analyzer) AppendError(file string, err error) {
if r.MatchString(err.Error()) { if r.MatchString(err.Error()) {
return return
} }
errors := []Error{} errors := make([]Error, 0)
if ferrs, ok := gosec.errors[file]; ok { if ferrs, ok := gosec.errors[file]; ok {
errors = ferrs errors = ferrs
} }
@ -364,9 +370,8 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
gosec.context.Imports.TrackImport(n) gosec.context.Imports.TrackImport(n)
for _, rule := range gosec.ruleset.RegisteredFor(n) { for _, rule := range gosec.ruleset.RegisteredFor(n) {
if _, ok := ignores[rule.ID()]; ok { _, ignored := ignores[rule.ID()]
continue
}
issue, err := rule.Match(n, gosec.context) issue, err := rule.Match(n, gosec.context)
if err != nil { if err != nil {
file, line := GetLocation(n, gosec.context) file, line := GetLocation(n, gosec.context)
@ -374,9 +379,16 @@ 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) gosec.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
} }
if issue != nil { if issue != nil {
gosec.issues = append(gosec.issues, issue) if gosec.showIgnored {
issue.NoSec = ignored
}
if !ignored || !gosec.showIgnored {
gosec.stats.NumFound++ gosec.stats.NumFound++
} }
if !ignored || gosec.showIgnored || gosec.ignoreNosec {
gosec.issues = append(gosec.issues, issue)
}
}
} }
return gosec return gosec
} }

View file

@ -261,6 +261,32 @@ var _ = Describe("Analyzer", func() {
Expect(nosecIssues).Should(HaveLen(sample.Errors)) 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() { It("should be possible to use an alternative nosec tag", func() {
// Rule for MD5 weak crypto usage // Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0] sample := testutils.SampleCodeG401[0]

View file

@ -72,6 +72,9 @@ var (
// #nosec flag // #nosec flag
flagIgnoreNoSec = flag.Bool("nosec", false, "Ignores #nosec comments when set") 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 // format output
flagFormat = flag.String("fmt", "text", "Set output format. Valid options are: json, yaml, csv, junit-xml, html, sonarqube, golint, sarif or text") 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 { if *flagIgnoreNoSec {
config.SetGlobal(gosec.Nosec, "true") config.SetGlobal(gosec.Nosec, "true")
} }
if *flagShowIgnored {
config.SetGlobal(gosec.ShowIgnored, "true")
}
if *flagAlternativeNoSec != "" { if *flagAlternativeNoSec != "" {
config.SetGlobal(gosec.NoSecAlternative, *flagAlternativeNoSec) config.SetGlobal(gosec.NoSecAlternative, *flagAlternativeNoSec)
} }
@ -200,7 +206,7 @@ func loadRules(include, exclude string) rules.RuleList {
} }
func getRootPaths(paths []string) []string { func getRootPaths(paths []string) []string {
rootPaths := []string{} rootPaths := make([]string, 0)
for _, path := range paths { for _, path := range paths {
rootPath, err := gosec.RootPath(path) rootPath, err := gosec.RootPath(path)
if err != nil { 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 { func filterIssues(issues []*gosec.Issue, severity gosec.Score, confidence gosec.Score) ([]*gosec.Issue, int) {
result := []*gosec.Issue{} result := make([]*gosec.Issue, 0)
trueIssues := 0
for _, issue := range issues { for _, issue := range issues {
if issue.Severity >= severity && issue.Confidence >= confidence { if issue.Severity >= severity && issue.Confidence >= confidence {
result = append(result, issue) result = append(result, issue)
if !issue.NoSec || !*flagShowIgnored {
trueIssues++
} }
} }
return result }
return result, trueIssues
} }
func main() { func main() {
@ -372,9 +382,10 @@ func main() {
} }
// Filter the issues by severity and confidence // Filter the issues by severity and confidence
issues = filterIssues(issues, failSeverity, failConfidence) var trueIssues int
if metrics.NumFound != len(issues) { issues, trueIssues = filterIssues(issues, failSeverity, failConfidence)
metrics.NumFound = len(issues) if metrics.NumFound != trueIssues {
metrics.NumFound = trueIssues
} }
// Exit quietly if nothing was found // Exit quietly if nothing was found
@ -390,7 +401,7 @@ func main() {
if *flagOutput == "" || *flagStdOut { if *flagOutput == "" || *flagStdOut {
fileFormat := getPrintedFormat(*flagFormat, *flagVerbose) fileFormat := getPrintedFormat(*flagFormat, *flagVerbose)
if err := printReport(fileFormat, *flagColor, rootPaths, reportInfo); err != nil { if err := printReport(fileFormat, *flagColor, rootPaths, reportInfo); err != nil {
logger.Fatal((err)) logger.Fatal(err)
} }
} }
if *flagOutput != "" { if *flagOutput != "" {

View file

@ -20,6 +20,8 @@ type GlobalOption string
const ( const (
// Nosec global option for #nosec directive // Nosec global option for #nosec directive
Nosec GlobalOption = "nosec" 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 global option which indicates that gosec runs in audit mode
Audit GlobalOption = "audit" Audit GlobalOption = "audit"
// NoSecAlternative global option alternative for #nosec directive // NoSecAlternative global option alternative for #nosec directive

View file

@ -97,6 +97,7 @@ type Issue struct {
Code string `json:"code"` // Impacted code line Code string `json:"code"` // Impacted code line
Line string `json:"line"` // Line number in file Line string `json:"line"` // Line number in file
Col string `json:"column"` // Column number in line 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 // FileLocation point out the file path and line number in file

View file

@ -62,6 +62,8 @@ const templateContent = `
level += " is-warning"; level += " is-warning";
} else if (this.props.level === "LOW") { } else if (this.props.level === "LOW") {
level += " is-info"; level += " is-info";
} else if (this.props.level === "WAIVED") {
level += " is-success";
} }
level +=" is-rounded"; level +=" is-rounded";
return ( return (
@ -96,6 +98,7 @@ const templateContent = `
</div> </div>
<div className="column is-one-quarter"> <div className="column is-one-quarter">
<div className="field is-grouped is-grouped-multiline"> <div className="field is-grouped is-grouped-multiline">
{this.props.data.nosec && <IssueTag label="NoSec" level="WAIVED"/>}
<IssueTag label="Severity" level={ this.props.data.severity }/> <IssueTag label="Severity" level={ this.props.data.severity }/>
<IssueTag label="Confidence" level={ this.props.data.confidence }/> <IssueTag label="Confidence" level={ this.props.data.confidence }/>
</div> </div>

View file

@ -8,7 +8,7 @@ Golang errors in file: [{{ $filePath }}]:
{{end}} {{end}}
{{end}} {{end}}
{{ range $index, $issue := .Issues }} {{ 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 }} {{ printCode $issue }}
{{ end }} {{ end }}

View file

@ -45,7 +45,7 @@ func plainTextFuncMap(enableColor bool) template.FuncMap {
// by default those functions return the given content untouched // by default those functions return the given content untouched
return template.FuncMap{ return template.FuncMap{
"highlight": func(t string, s gosec.Score) string { "highlight": func(t string, s gosec.Score, ignored bool) string {
return t return t
}, },
"danger": fmt.Sprint, "danger": fmt.Sprint,
@ -56,7 +56,10 @@ func plainTextFuncMap(enableColor bool) template.FuncMap {
} }
// highlight returns content t colored based on Score // 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 { switch s {
case gosec.High: case gosec.High:
return errorTheme.Sprint(t) return errorTheme.Sprint(t)