Add support for suppressing the findings

This commit is contained in:
Yiwei Ding 2021-12-09 18:53:36 +08:00 committed by GitHub
parent 040327f7d7
commit b45f95f6ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 448 additions and 127 deletions

View file

@ -269,7 +269,8 @@ gosec -exclude-generated ./...
### Annotating code ### Annotating code
As with all automated detection tools, there will be cases of false positives. In cases where gosec reports a failure that has been manually verified as being safe, As with all automated detection tools, there will be cases of false positives. In cases where gosec reports a failure that has been manually verified as being safe,
it is possible to annotate the code with a `#nosec` comment. it is possible to annotate the code with a comment that starts with `#nosec`.
The `#nosec` comment should have the format `#nosec [RuleList] [-- Justification]`.
The annotation causes gosec to stop processing any further nodes within the The annotation causes gosec to stop processing any further nodes within the
AST so can apply to a whole block or more granularly to a single expression. AST so can apply to a whole block or more granularly to a single expression.
@ -294,6 +295,10 @@ When a specific false positive has been identified and verified as safe, you may
within a section of code, while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within within a section of code, while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within
the `#nosec` annotation, e.g: `/* #nosec G401 */` or `// #nosec G201 G202 G203` the `#nosec` annotation, e.g: `/* #nosec G401 */` or `// #nosec G201 G202 G203`
You could put the description or justification text for the annotation. The
justification should be after the rule(s) to suppress and start with two or
more dashes, e.g: `// #nosec G101 G102 -- This is a false positive`
In some cases you may also want to revisit places where `#nosec` annotations In some cases you may also want to revisit places where `#nosec` annotations
have been used. To run the scanner and ignore any `#nosec` annotations you have been used. To run the scanner and ignore any `#nosec` annotations you
can do the following: can do the following:
@ -302,6 +307,27 @@ can do the following:
gosec -nosec=true ./... gosec -nosec=true ./...
``` ```
### Tracking suppressions
As described above, we could suppress violations externally (using `-include`/
`-exclude`) or inline (using `#nosec` annotations) in gosec. This suppression
inflammation can be used to generate corresponding signals for auditing
purposes.
We could track suppressions by the `-track-suppressions` flag as follows:
```bash
gosec -track-suppressions -exclude=G101 -fmt=sarif -out=results.sarif ./...
```
- For external suppressions, gosec records suppression info where `kind` is
`external` and `justification` is a certain sentence "Globally suppressed".
- For inline suppressions, gosec records suppression info where `kind` is
`inSource` and `justification` is the text after two or more dashes in the
comment.
**Note:** Only SARIF and JSON formats support tracking suppressions.
### Build tags ### Build tags
gosec is able to pass your [Go build tags](https://golang.org/pkg/go/build/) to the analyzer. gosec is able to pass your [Go build tags](https://golang.org/pkg/go/build/) to the analyzer.

View file

@ -43,6 +43,10 @@ const LoadMode = packages.NeedName |
packages.NeedTypesInfo | packages.NeedTypesInfo |
packages.NeedSyntax packages.NeedSyntax
const externalSuppressionJustification = "Globally suppressed."
const aliasOfAllRules = "*"
var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
// The Context is populated with data parsed from the source code as it is scanned. // The Context is populated with data parsed from the source code as it is scanned.
@ -57,7 +61,7 @@ type Context struct {
Root *ast.File Root *ast.File
Config Config Config Config
Imports *ImportTracker Imports *ImportTracker
Ignores []map[string]bool Ignores []map[string][]SuppressionInfo
PassedValues map[string]interface{} PassedValues map[string]interface{}
} }
@ -72,21 +76,29 @@ type Metrics struct {
// Analyzer object is the main object of gosec. It has methods traverse an AST // Analyzer object is the main object of gosec. It has methods traverse an AST
// and invoke the correct checking rules as on each node as required. // and invoke the correct checking rules as on each node as required.
type Analyzer struct { type Analyzer struct {
ignoreNosec bool ignoreNosec bool
ruleset RuleSet ruleset RuleSet
context *Context context *Context
config Config config Config
logger *log.Logger logger *log.Logger
issues []*Issue issues []*Issue
stats *Metrics stats *Metrics
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 showIgnored bool
trackSuppressions bool
}
// SuppressionInfo object is to record the kind and the justification that used
// to suppress violations.
type SuppressionInfo struct {
Kind string `json:"kind"`
Justification string `json:"justification"`
} }
// NewAnalyzer builds a new analyzer. // NewAnalyzer builds a new analyzer.
func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, logger *log.Logger) *Analyzer { func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, trackSuppressions bool, logger *log.Logger) *Analyzer {
ignoreNoSec := false ignoreNoSec := false
if enabled, err := conf.IsGlobalEnabled(Nosec); err == nil { if enabled, err := conf.IsGlobalEnabled(Nosec); err == nil {
ignoreNoSec = enabled ignoreNoSec = enabled
@ -99,17 +111,18 @@ func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, logger *log.Log
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, showIgnored: showIgnored,
ruleset: make(RuleSet), ruleset: NewRuleSet(),
context: &Context{}, context: &Context{},
config: conf, config: conf,
logger: logger, logger: logger,
issues: make([]*Issue, 0, 16), issues: make([]*Issue, 0, 16),
stats: &Metrics{}, stats: &Metrics{},
errors: make(map[string][]Error), errors: make(map[string][]Error),
tests: tests, tests: tests,
excludeGenerated: excludeGenerated, excludeGenerated: excludeGenerated,
trackSuppressions: trackSuppressions,
} }
} }
@ -125,10 +138,10 @@ func (gosec *Analyzer) Config() Config {
// LoadRules instantiates all the rules to be used when analyzing source // LoadRules instantiates all the rules to be used when analyzing source
// packages // packages
func (gosec *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder) { func (gosec *Analyzer) LoadRules(ruleDefinitions map[string]RuleBuilder, ruleSuppressed map[string]bool) {
for id, def := range ruleDefinitions { for id, def := range ruleDefinitions {
r, nodes := def(id, gosec.config) r, nodes := def(id, gosec.config)
gosec.ruleset.Register(r, nodes...) gosec.ruleset.Register(r, ruleSuppressed[id], nodes...)
} }
} }
@ -295,7 +308,7 @@ func (gosec *Analyzer) AppendError(file string, err error) {
} }
// ignore a node (and sub-tree) if it is tagged with a nosec tag comment // ignore a node (and sub-tree) if it is tagged with a nosec tag comment
func (gosec *Analyzer) ignore(n ast.Node) ([]string, bool) { func (gosec *Analyzer) ignore(n ast.Node) map[string]SuppressionInfo {
if groups, ok := gosec.context.Comments[n]; ok && !gosec.ignoreNosec { if groups, ok := gosec.context.Comments[n]; ok && !gosec.ignoreNosec {
// Checks if an alternative for #nosec is set and, if not, uses the default. // Checks if an alternative for #nosec is set and, if not, uses the default.
@ -307,31 +320,44 @@ func (gosec *Analyzer) ignore(n ast.Node) ([]string, bool) {
for _, group := range groups { for _, group := range groups {
foundDefaultTag := strings.Contains(group.Text(), noSecDefaultTag) foundDefaultTag := strings.HasPrefix(group.Text(), noSecDefaultTag)
foundAlternativeTag := strings.Contains(group.Text(), noSecAlternativeTag) foundAlternativeTag := strings.HasPrefix(group.Text(), noSecAlternativeTag)
if foundDefaultTag || foundAlternativeTag { if foundDefaultTag || foundAlternativeTag {
gosec.stats.NumNosec++ gosec.stats.NumNosec++
// Extract the directive and the justification.
justification := ""
commentParts := regexp.MustCompile(`-{2,}`).Split(group.Text(), 2)
directive := commentParts[0]
if len(commentParts) > 1 {
justification = strings.TrimSpace(strings.TrimRight(commentParts[1], "\n"))
}
// Pull out the specific rules that are listed to be ignored. // Pull out the specific rules that are listed to be ignored.
re := regexp.MustCompile(`(G\d{3})`) re := regexp.MustCompile(`(G\d{3})`)
matches := re.FindAllStringSubmatch(group.Text(), -1) matches := re.FindAllStringSubmatch(directive, -1)
// If no specific rules were given, ignore everything. suppression := SuppressionInfo{
if len(matches) == 0 { Kind: "inSource",
return nil, true Justification: justification,
} }
// Find the rule IDs to ignore. // Find the rule IDs to ignore.
var ignores []string ignores := make(map[string]SuppressionInfo)
for _, v := range matches { for _, v := range matches {
ignores = append(ignores, v[1]) ignores[v[1]] = suppression
} }
return ignores, false
// If no specific rules were given, ignore everything.
if len(matches) == 0 {
ignores[aliasOfAllRules] = suppression
}
return ignores
} }
} }
} }
return nil, false return nil
} }
// Visit runs the gosec visitor logic over an AST created by parsing go code. // Visit runs the gosec visitor logic over an AST created by parsing go code.
@ -346,31 +372,40 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
} }
// Get any new rule exclusions. // Get any new rule exclusions.
ignoredRules, ignoreAll := gosec.ignore(n) ignoredRules := gosec.ignore(n)
if ignoreAll {
return nil
}
// Now create the union of exclusions. // Now create the union of exclusions.
ignores := map[string]bool{} ignores := map[string][]SuppressionInfo{}
if len(gosec.context.Ignores) > 0 { if len(gosec.context.Ignores) > 0 {
for k, v := range gosec.context.Ignores[0] { for k, v := range gosec.context.Ignores[0] {
ignores[k] = v ignores[k] = v
} }
} }
for _, v := range ignoredRules { for ruleID, suppression := range ignoredRules {
ignores[v] = true ignores[ruleID] = append(ignores[ruleID], suppression)
} }
// Push the new set onto the stack. // Push the new set onto the stack.
gosec.context.Ignores = append([]map[string]bool{ignores}, gosec.context.Ignores...) gosec.context.Ignores = append([]map[string][]SuppressionInfo{ignores}, gosec.context.Ignores...)
// Track aliased and initialization imports // Track aliased and initialization imports
gosec.context.Imports.TrackImport(n) gosec.context.Imports.TrackImport(n)
for _, rule := range gosec.ruleset.RegisteredFor(n) { for _, rule := range gosec.ruleset.RegisteredFor(n) {
_, ignored := ignores[rule.ID()] // Check if all rules are ignored.
suppressions, ignored := ignores[aliasOfAllRules]
if !ignored {
suppressions, ignored = ignores[rule.ID()]
}
// Track external suppressions.
if gosec.ruleset.IsRuleSuppressed(rule.ID()) {
ignored = true
suppressions = append(suppressions, SuppressionInfo{
Kind: "external",
Justification: externalSuppressionJustification,
})
}
issue, err := rule.Match(n, gosec.context) issue, err := rule.Match(n, gosec.context)
if err != nil { if err != nil {
@ -385,7 +420,10 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
if !ignored || !gosec.showIgnored { if !ignored || !gosec.showIgnored {
gosec.stats.NumFound++ gosec.stats.NumFound++
} }
if !ignored || gosec.showIgnored || gosec.ignoreNosec { if ignored && gosec.trackSuppressions {
issue.WithSuppressions(suppressions)
gosec.issues = append(gosec.issues, issue)
} else if !ignored || gosec.showIgnored || gosec.ignoreNosec {
gosec.issues = append(gosec.issues, issue) gosec.issues = append(gosec.issues, issue)
} }
} }

View file

@ -24,12 +24,12 @@ var _ = Describe("Analyzer", func() {
) )
BeforeEach(func() { BeforeEach(func() {
logger, _ = testutils.NewLogger() logger, _ = testutils.NewLogger()
analyzer = gosec.NewAnalyzer(nil, tests, false, logger) analyzer = gosec.NewAnalyzer(nil, tests, false, false, logger)
}) })
Context("when processing a package", func() { Context("when processing a package", func() {
It("should not report an error if the package contains no Go files", func() { It("should not report an error if the package contains no Go files", func() {
analyzer.LoadRules(rules.Generate().Builders()) analyzer.LoadRules(rules.Generate(false).RulesInfo())
dir, err := ioutil.TempDir("", "empty") dir, err := ioutil.TempDir("", "empty")
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
Expect(err).ShouldNot(HaveOccurred()) Expect(err).ShouldNot(HaveOccurred())
@ -40,7 +40,7 @@ var _ = Describe("Analyzer", func() {
}) })
It("should report an error if the package fails to build", func() { It("should report an error if the package fails to build", func() {
analyzer.LoadRules(rules.Generate().Builders()) analyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("wonky.go", `func main(){ println("forgot the package")}`) pkg.AddFile("wonky.go", `func main(){ println("forgot the package")}`)
@ -56,7 +56,7 @@ var _ = Describe("Analyzer", func() {
}) })
It("should be able to analyze multiple Go files", func() { It("should be able to analyze multiple Go files", func() {
analyzer.LoadRules(rules.Generate().Builders()) analyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("foo.go", ` pkg.AddFile("foo.go", `
@ -78,7 +78,7 @@ var _ = Describe("Analyzer", func() {
}) })
It("should be able to analyze multiple Go packages", func() { It("should be able to analyze multiple Go packages", func() {
analyzer.LoadRules(rules.Generate().Builders()) analyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg1 := testutils.NewTestPackage() pkg1 := testutils.NewTestPackage()
pkg2 := testutils.NewTestPackage() pkg2 := testutils.NewTestPackage()
defer pkg1.Close() defer pkg1.Close()
@ -104,7 +104,7 @@ var _ = Describe("Analyzer", func() {
It("should find errors when nosec is not in use", func() { It("should find errors when nosec is not in use", func() {
sample := testutils.SampleCodeG401[0] sample := testutils.SampleCodeG401[0]
source := sample.Code[0] source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
controlPackage := testutils.NewTestPackage() controlPackage := testutils.NewTestPackage()
defer controlPackage.Close() defer controlPackage.Close()
@ -118,7 +118,7 @@ var _ = Describe("Analyzer", func() {
}) })
It("should report Go build errors and invalid files", func() { It("should report Go build errors and invalid files", func() {
analyzer.LoadRules(rules.Generate().Builders()) analyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("foo.go", ` pkg.AddFile("foo.go", `
@ -142,7 +142,7 @@ var _ = Describe("Analyzer", func() {
It("should not report errors when a nosec comment is present", func() { It("should not report errors when a nosec comment is present", func() {
sample := testutils.SampleCodeG401[0] sample := testutils.SampleCodeG401[0]
source := sample.Code[0] source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -160,7 +160,7 @@ var _ = Describe("Analyzer", func() {
// Rule for MD5 weak crypto usage // Rule for MD5 weak crypto usage
sample := testutils.SampleCodeG401[0] sample := testutils.SampleCodeG401[0]
source := sample.Code[0] source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -177,7 +177,7 @@ var _ = Describe("Analyzer", func() {
It("should report errors when an exclude comment is present for a different rule", func() { It("should report errors when an exclude comment is present for a different rule", func() {
sample := testutils.SampleCodeG401[0] sample := testutils.SampleCodeG401[0]
source := sample.Code[0] source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -194,7 +194,7 @@ var _ = Describe("Analyzer", func() {
It("should not report errors when an exclude comment is present for multiple rules, including the correct rule", func() { It("should not report errors when an exclude comment is present for multiple rules, including the correct rule", func() {
sample := testutils.SampleCodeG401[0] sample := testutils.SampleCodeG401[0]
source := sample.Code[0] source := sample.Code[0]
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -213,7 +213,7 @@ var _ = Describe("Analyzer", func() {
It("should pass the build tags", func() { It("should pass the build tags", func() {
sample := testutils.SampleCodeBuildTag[0] sample := testutils.SampleCodeBuildTag[0]
source := sample.Code[0] source := sample.Code[0]
analyzer.LoadRules(rules.Generate().Builders()) analyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("tags.go", source) pkg.AddFile("tags.go", source)
@ -223,7 +223,7 @@ var _ = Describe("Analyzer", func() {
}) })
It("should process an empty package with test file", func() { It("should process an empty package with test file", func() {
analyzer.LoadRules(rules.Generate().Builders()) analyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("foo_test.go", ` pkg.AddFile("foo_test.go", `
@ -245,8 +245,8 @@ var _ = Describe("Analyzer", func() {
// overwrite nosec option // overwrite nosec option
nosecIgnoreConfig := gosec.NewConfig() nosecIgnoreConfig := gosec.NewConfig()
nosecIgnoreConfig.SetGlobal(gosec.Nosec, "true") nosecIgnoreConfig.SetGlobal(gosec.Nosec, "true")
customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, logger) customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, false, logger)
customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) customAnalyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -269,8 +269,8 @@ var _ = Describe("Analyzer", func() {
nosecIgnoreConfig := gosec.NewConfig() nosecIgnoreConfig := gosec.NewConfig()
nosecIgnoreConfig.SetGlobal(gosec.Nosec, "true") nosecIgnoreConfig.SetGlobal(gosec.Nosec, "true")
nosecIgnoreConfig.SetGlobal(gosec.ShowIgnored, "true") nosecIgnoreConfig.SetGlobal(gosec.ShowIgnored, "true")
customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, logger) customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, false, logger)
customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) customAnalyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -294,8 +294,8 @@ var _ = Describe("Analyzer", func() {
// overwrite nosec option // overwrite nosec option
nosecIgnoreConfig := gosec.NewConfig() nosecIgnoreConfig := gosec.NewConfig()
nosecIgnoreConfig.SetGlobal(gosec.NoSecAlternative, "#falsePositive") nosecIgnoreConfig.SetGlobal(gosec.NoSecAlternative, "#falsePositive")
customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, logger) customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, false, logger)
customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) customAnalyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -317,8 +317,8 @@ var _ = Describe("Analyzer", func() {
// overwrite nosec option // overwrite nosec option
nosecIgnoreConfig := gosec.NewConfig() nosecIgnoreConfig := gosec.NewConfig()
nosecIgnoreConfig.SetGlobal(gosec.NoSecAlternative, "#falsePositive") nosecIgnoreConfig.SetGlobal(gosec.NoSecAlternative, "#falsePositive")
customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, logger) customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, false, logger)
customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) customAnalyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage() nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close() defer nosecPackage.Close()
@ -333,8 +333,8 @@ var _ = Describe("Analyzer", func() {
}) })
It("should be able to analyze Go test package", func() { It("should be able to analyze Go test package", func() {
customAnalyzer := gosec.NewAnalyzer(nil, true, false, logger) customAnalyzer := gosec.NewAnalyzer(nil, true, false, false, logger)
customAnalyzer.LoadRules(rules.Generate().Builders()) customAnalyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("foo.go", ` pkg.AddFile("foo.go", `
@ -358,8 +358,8 @@ var _ = Describe("Analyzer", func() {
Expect(issues).Should(HaveLen(1)) Expect(issues).Should(HaveLen(1))
}) })
It("should be able to scan generated files if NOT excluded", func() { It("should be able to scan generated files if NOT excluded", func() {
customAnalyzer := gosec.NewAnalyzer(nil, true, false, logger) customAnalyzer := gosec.NewAnalyzer(nil, true, false, false, logger)
customAnalyzer.LoadRules(rules.Generate().Builders()) customAnalyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("foo.go", ` pkg.AddFile("foo.go", `
@ -379,8 +379,8 @@ var _ = Describe("Analyzer", func() {
Expect(issues).Should(HaveLen(1)) Expect(issues).Should(HaveLen(1))
}) })
It("should be able to skip generated files if excluded", func() { It("should be able to skip generated files if excluded", func() {
customAnalyzer := gosec.NewAnalyzer(nil, true, true, logger) customAnalyzer := gosec.NewAnalyzer(nil, true, true, false, logger)
customAnalyzer.LoadRules(rules.Generate().Builders()) customAnalyzer.LoadRules(rules.Generate(false).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
pkg.AddFile("foo.go", ` pkg.AddFile("foo.go", `
@ -401,7 +401,7 @@ var _ = Describe("Analyzer", func() {
}) })
}) })
It("should be able to analyze Cgo files", func() { It("should be able to analyze Cgo files", func() {
analyzer.LoadRules(rules.Generate().Builders()) analyzer.LoadRules(rules.Generate(false).RulesInfo())
sample := testutils.SampleCodeCgo[0] sample := testutils.SampleCodeCgo[0]
source := sample.Code[0] source := sample.Code[0]
@ -583,4 +583,106 @@ var _ = Describe("Analyzer", func() {
} }
}) })
}) })
Context("when tracking suppressions", func() {
BeforeEach(func() {
analyzer = gosec.NewAnalyzer(nil, tests, false, true, logger)
})
It("should not report an error if the violation is suppressed", func() {
sample := testutils.SampleCodeG401[0]
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close()
nosecSource := strings.Replace(source, "h := md5.New()", "h := md5.New() // #nosec G401 -- Justification", 1)
nosecPackage.AddFile("md5.go", nosecSource)
err := nosecPackage.Build()
Expect(err).ShouldNot(HaveOccurred())
err = analyzer.Process(buildTags, nosecPackage.Path)
Expect(err).ShouldNot(HaveOccurred())
issues, _, _ := analyzer.Report()
Expect(issues).To(HaveLen(1))
Expect(issues[0].Suppressions).To(HaveLen(1))
Expect(issues[0].Suppressions[0].Kind).To(Equal("inSource"))
Expect(issues[0].Suppressions[0].Justification).To(Equal("Justification"))
})
It("should not report an error if the violation is suppressed without certain rules", func() {
sample := testutils.SampleCodeG401[0]
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G401")).RulesInfo())
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 = analyzer.Process(buildTags, nosecPackage.Path)
Expect(err).ShouldNot(HaveOccurred())
issues, _, _ := analyzer.Report()
Expect(issues).To(HaveLen(1))
Expect(issues[0].Suppressions).To(HaveLen(1))
Expect(issues[0].Suppressions[0].Kind).To(Equal("inSource"))
Expect(issues[0].Suppressions[0].Justification).To(Equal(""))
})
It("should not report an error if the rule is not included", func() {
sample := testutils.SampleCodeG101[0]
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(true, rules.NewRuleFilter(false, "G401")).RulesInfo())
controlPackage := testutils.NewTestPackage()
defer controlPackage.Close()
controlPackage.AddFile("pwd.go", source)
err := controlPackage.Build()
Expect(err).ShouldNot(HaveOccurred())
err = analyzer.Process(buildTags, controlPackage.Path)
Expect(err).ShouldNot(HaveOccurred())
controlIssues, _, _ := analyzer.Report()
Expect(controlIssues).Should(HaveLen(sample.Errors))
Expect(controlIssues[0].Suppressions).To(HaveLen(1))
Expect(controlIssues[0].Suppressions[0].Kind).To(Equal("external"))
Expect(controlIssues[0].Suppressions[0].Justification).To(Equal("Globally suppressed."))
})
It("should not report an error if the rule is excluded", func() {
sample := testutils.SampleCodeG101[0]
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(true, rules.NewRuleFilter(true, "G101")).RulesInfo())
controlPackage := testutils.NewTestPackage()
defer controlPackage.Close()
controlPackage.AddFile("pwd.go", source)
err := controlPackage.Build()
Expect(err).ShouldNot(HaveOccurred())
err = analyzer.Process(buildTags, controlPackage.Path)
Expect(err).ShouldNot(HaveOccurred())
issues, _, _ := analyzer.Report()
Expect(issues).Should(HaveLen(sample.Errors))
Expect(issues[0].Suppressions).To(HaveLen(1))
Expect(issues[0].Suppressions[0].Kind).To(Equal("external"))
Expect(issues[0].Suppressions[0].Justification).To(Equal("Globally suppressed."))
})
It("should track multiple suppressions if the violation is multiply suppressed", func() {
sample := testutils.SampleCodeG101[0]
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(true, rules.NewRuleFilter(true, "G101")).RulesInfo())
nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close()
nosecSource := strings.Replace(source, "}", "} // #nosec G101 -- Justification", 1)
nosecPackage.AddFile("pwd.go", nosecSource)
err := nosecPackage.Build()
Expect(err).ShouldNot(HaveOccurred())
err = analyzer.Process(buildTags, nosecPackage.Path)
Expect(err).ShouldNot(HaveOccurred())
issues, _, _ := analyzer.Report()
Expect(issues).Should(HaveLen(sample.Errors))
Expect(issues[0].Suppressions).To(HaveLen(2))
})
})
}) })

View file

@ -132,6 +132,9 @@ var (
// overrides the output format when stdout the results while saving them in the output file // overrides the output format when stdout the results while saving them in the output file
flagVerbose = flag.String("verbose", "", "Overrides the output format when stdout the results while saving them in the output file.\nValid options are: json, yaml, csv, junit-xml, html, sonarqube, golint, sarif or text") flagVerbose = flag.String("verbose", "", "Overrides the output format when stdout the results while saving them in the output file.\nValid options are: json, yaml, csv, junit-xml, html, sonarqube, golint, sarif or text")
// output suppression information for auditing purposes
flagTrackSuppressions = flag.Bool("track-suppressions", false, "Output suppression information, including its kind and justification")
// exlude the folders from scan // exlude the folders from scan
flagDirsExclude arrayFlags flagDirsExclude arrayFlags
@ -147,14 +150,14 @@ func usage() {
fmt.Fprint(os.Stderr, "\n\nRULES:\n\n") fmt.Fprint(os.Stderr, "\n\nRULES:\n\n")
// sorted rule list for ease of reading // sorted rule list for ease of reading
rl := rules.Generate() rl := rules.Generate(*flagTrackSuppressions)
keys := make([]string, 0, len(rl)) keys := make([]string, 0, len(rl.Rules))
for key := range rl { for key := range rl.Rules {
keys = append(keys, key) keys = append(keys, key)
} }
sort.Strings(keys) sort.Strings(keys)
for _, k := range keys { for _, k := range keys {
v := rl[k] v := rl.Rules[k]
fmt.Fprintf(os.Stderr, "\t%s: %s\n", k, v.Description) fmt.Fprintf(os.Stderr, "\t%s: %s\n", k, v.Description)
} }
fmt.Fprint(os.Stderr, "\n") fmt.Fprint(os.Stderr, "\n")
@ -202,7 +205,7 @@ func loadRules(include, exclude string) rules.RuleList {
} else { } else {
logger.Println("Excluding rules: default") logger.Println("Excluding rules: default")
} }
return rules.Generate(filters...) return rules.Generate(*flagTrackSuppressions, filters...)
} }
func getRootPaths(paths []string) []string { func getRootPaths(paths []string) []string {
@ -267,7 +270,7 @@ func filterIssues(issues []*gosec.Issue, severity gosec.Score, confidence gosec.
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 { if (!issue.NoSec || !*flagShowIgnored) && len(issue.Suppressions) == 0 {
trueIssues++ trueIssues++
} }
} }
@ -345,14 +348,14 @@ func main() {
} }
// Load enabled rule definitions // Load enabled rule definitions
ruleDefinitions := loadRules(*flagRulesInclude, flagRulesExclude.String()) ruleList := loadRules(*flagRulesInclude, flagRulesExclude.String())
if len(ruleDefinitions) == 0 { if len(ruleList.Rules) == 0 {
logger.Fatal("No rules are configured") logger.Fatal("No rules are configured")
} }
// Create the analyzer // Create the analyzer
analyzer := gosec.NewAnalyzer(config, *flagScanTests, *flagExcludeGenerated, logger) analyzer := gosec.NewAnalyzer(config, *flagScanTests, *flagExcludeGenerated, *flagTrackSuppressions, logger)
analyzer.LoadRules(ruleDefinitions.Builders()) analyzer.LoadRules(ruleList.RulesInfo())
excludedDirs := gosec.ExcludedDirsRegExp(flagDirsExclude) excludedDirs := gosec.ExcludedDirsRegExp(flagDirsExclude)
var packages []string var packages []string

View file

@ -104,7 +104,7 @@ func dumpAst(files ...string) {
continue continue
} }
// Print the AST. #nosec // #nosec -- Print the AST.
ast.Print(fset, f) ast.Print(fset, f)
} }
} }

View file

@ -88,16 +88,17 @@ var ruleToCWE = map[string]string{
// Issue is returned by a gosec rule if it discovers an issue with the scanned code. // Issue is returned by a gosec rule if it discovers an issue with the scanned code.
type Issue struct { type Issue struct {
Severity Score `json:"severity"` // issue severity (how problematic it is) Severity Score `json:"severity"` // issue severity (how problematic it is)
Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it) Confidence Score `json:"confidence"` // issue confidence (how sure we are we found it)
Cwe *cwe.Weakness `json:"cwe"` // Cwe associated with RuleID Cwe *cwe.Weakness `json:"cwe"` // Cwe associated with RuleID
RuleID string `json:"rule_id"` // Human readable explanation RuleID string `json:"rule_id"` // Human readable explanation
What string `json:"details"` // Human readable explanation What string `json:"details"` // Human readable explanation
File string `json:"file"` // File name we found it in File string `json:"file"` // File name we found it in
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 NoSec bool `json:"nosec"` // true if the issue is nosec
Suppressions []SuppressionInfo `json:"suppressions"` // Suppression info of the issue
} }
// FileLocation point out the file path and line number in file // FileLocation point out the file path and line number in file
@ -200,3 +201,9 @@ func NewIssue(ctx *Context, node ast.Node, ruleID, desc string, severity Score,
Cwe: GetCweByRule(ruleID), Cwe: GetCweByRule(ruleID),
} }
} }
// WithSuppressions set the suppressions of the issue
func (i *Issue) WithSuppressions(suppressions []SuppressionInfo) *Issue {
i.Suppressions = suppressions
return i
}

View file

@ -53,6 +53,9 @@ const (
// the specified format. The formats currently accepted are: json, yaml, csv, junit-xml, html, sonarqube, golint and text. // the specified format. The formats currently accepted are: json, yaml, csv, junit-xml, html, sonarqube, golint and text.
func CreateReport(w io.Writer, format string, enableColor bool, rootPaths []string, data *gosec.ReportInfo) error { func CreateReport(w io.Writer, format string, enableColor bool, rootPaths []string, data *gosec.ReportInfo) error {
var err error var err error
if format != "json" && format != "sarif" {
data.Issues = filterOutSuppressedIssues(data.Issues)
}
switch format { switch format {
case "json": case "json":
err = json.WriteReport(w, data) err = json.WriteReport(w, data)
@ -77,3 +80,13 @@ func CreateReport(w io.Writer, format string, enableColor bool, rootPaths []stri
} }
return err return err
} }
func filterOutSuppressedIssues(issues []*gosec.Issue) []*gosec.Issue {
nonSuppressedIssues := []*gosec.Issue{}
for _, issue := range issues {
if len(issue.Suppressions) == 0 {
nonSuppressedIssues = append(nonSuppressedIssues, issue)
}
}
return nonSuppressedIssues
}

View file

@ -486,4 +486,53 @@ var _ = Describe("Formatter", func() {
} }
}) })
}) })
Context("When converting suppressed issues", func() {
ruleID := "G101"
cwe := gosec.GetCweByRule(ruleID)
suppressions := []gosec.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([]*gosec.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([]*gosec.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([]*gosec.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":[{`))
})
})
}) })

View file

@ -79,12 +79,13 @@ func NewTool(driver *ToolComponent) *Tool {
} }
// NewResult instantiate a Result // NewResult instantiate a Result
func NewResult(ruleID string, ruleIndex int, level Level, message string) *Result { func NewResult(ruleID string, ruleIndex int, level Level, message string, suppressions []*Suppression) *Result {
return &Result{ return &Result{
RuleID: ruleID, RuleID: ruleID,
RuleIndex: ruleIndex, RuleIndex: ruleIndex,
Level: level, Level: level,
Message: NewMessage(message), Message: NewMessage(message),
Suppressions: suppressions,
} }
} }
@ -199,3 +200,11 @@ func NewToolComponentReference(name string) *ToolComponentReference {
GUID: uuid3(name), GUID: uuid3(name),
} }
} }
// NewSuppression instantiate a Suppression
func NewSuppression(kind string, justification string) *Suppression {
return &Suppression{
Kind: kind,
Justification: justification,
}
}

View file

@ -48,7 +48,7 @@ func GenerateReport(rootPaths []string, data *gosec.ReportInfo) (*Report, error)
return nil, err return nil, err
} }
result := NewResult(r.rule.ID, r.index, getSarifLevel(issue.Severity.String()), issue.What). result := NewResult(r.rule.ID, r.index, getSarifLevel(issue.Severity.String()), issue.What, buildSarifSuppressions(issue.Suppressions)).
WithLocations(location) WithLocations(location)
results = append(results, result) results = append(results, result)
@ -199,3 +199,11 @@ func getSarifLevel(s string) Level {
return Note return Note
} }
} }
func buildSarifSuppressions(suppressions []gosec.SuppressionInfo) []*Suppression {
var sarifSuppressionList []*Suppression
for _, s := range suppressions {
sarifSuppressionList = append(sarifSuppressionList, NewSuppression(s.Kind, s.Justification))
}
return sarifSuppressionList
}

View file

@ -2,6 +2,7 @@ package sarif_test
import ( import (
"bytes" "bytes"
"regexp"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
@ -21,5 +22,39 @@ var _ = Describe("Sarif Formatter", func() {
Expect(err).ShouldNot(HaveOccurred()) Expect(err).ShouldNot(HaveOccurred())
Expect(result).To(ContainSubstring("\"results\": [")) Expect(result).To(ContainSubstring("\"results\": ["))
}) })
It("sarif formatted report should contain the suppressed results", func() {
ruleID := "G101"
cwe := gosec.GetCweByRule(ruleID)
suppressedIssue := gosec.Issue{
File: "/home/src/project/test.go",
Line: "1",
Col: "1",
RuleID: ruleID,
What: "test",
Confidence: gosec.High,
Severity: gosec.High,
Code: "1: testcode",
Cwe: cwe,
Suppressions: []gosec.SuppressionInfo{
{
Kind: "kind",
Justification: "justification",
},
},
}
reportInfo := gosec.NewReportInfo([]*gosec.Issue{&suppressedIssue}, &gosec.Metrics{}, map[string][]gosec.Error{}).WithVersion("v2.7.0")
buf := new(bytes.Buffer)
err := sarif.WriteReport(buf, reportInfo, []string{})
result := buf.String()
Expect(err).ShouldNot(HaveOccurred())
hasResults, _ := regexp.MatchString(`"results": \[(\s*){`, result)
Expect(hasResults).To(BeTrue())
hasSuppressions, _ := regexp.MatchString(`"suppressions": \[(\s*){`, result)
Expect(hasSuppressions).To(BeTrue())
})
}) })
}) })

27
rule.go
View file

@ -26,34 +26,45 @@ type Rule interface {
// RuleBuilder is used to register a rule definition with the analyzer // RuleBuilder is used to register a rule definition with the analyzer
type RuleBuilder func(id string, c Config) (Rule, []ast.Node) type RuleBuilder func(id string, c Config) (Rule, []ast.Node)
// A RuleSet maps lists of rules to the type of AST node they should be run on. // A RuleSet contains a mapping of lists of rules to the type of AST node they
// should be run on and a mapping of rule ID's to whether the rule are
// suppressed.
// The analyzer will only invoke rules contained in the list associated with the // The analyzer will only invoke rules contained in the list associated with the
// type of AST node it is currently visiting. // type of AST node it is currently visiting.
type RuleSet map[reflect.Type][]Rule type RuleSet struct {
Rules map[reflect.Type][]Rule
RuleSuppressedMap map[string]bool
}
// NewRuleSet constructs a new RuleSet // NewRuleSet constructs a new RuleSet
func NewRuleSet() RuleSet { func NewRuleSet() RuleSet {
return make(RuleSet) return RuleSet{make(map[reflect.Type][]Rule), make(map[string]bool)}
} }
// Register adds a trigger for the supplied rule for the the // Register adds a trigger for the supplied rule for the the
// specified ast nodes. // specified ast nodes.
func (r RuleSet) Register(rule Rule, nodes ...ast.Node) { func (r RuleSet) Register(rule Rule, isSuppressed bool, nodes ...ast.Node) {
for _, n := range nodes { for _, n := range nodes {
t := reflect.TypeOf(n) t := reflect.TypeOf(n)
if rules, ok := r[t]; ok { if rules, ok := r.Rules[t]; ok {
r[t] = append(rules, rule) r.Rules[t] = append(rules, rule)
} else { } else {
r[t] = []Rule{rule} r.Rules[t] = []Rule{rule}
} }
} }
r.RuleSuppressedMap[rule.ID()] = isSuppressed
} }
// RegisteredFor will return all rules that are registered for a // RegisteredFor will return all rules that are registered for a
// specified ast node. // specified ast node.
func (r RuleSet) RegisteredFor(n ast.Node) []Rule { func (r RuleSet) RegisteredFor(n ast.Node) []Rule {
if rules, found := r[reflect.TypeOf(n)]; found { if rules, found := r.Rules[reflect.TypeOf(n)]; found {
return rules return rules
} }
return []Rule{} return []Rule{}
} }
// IsRuleSuppressed will return whether the rule is suppressed.
func (r RuleSet) IsRuleSuppressed(ruleID string) bool {
return r.RuleSuppressedMap[ruleID]
}

View file

@ -59,26 +59,36 @@ var _ = Describe("Rule", func() {
registeredNodeB := (*ast.AssignStmt)(nil) registeredNodeB := (*ast.AssignStmt)(nil)
unregisteredNode := (*ast.BinaryExpr)(nil) unregisteredNode := (*ast.BinaryExpr)(nil)
ruleset.Register(dummyIssueRule, registeredNodeA, registeredNodeB) ruleset.Register(dummyIssueRule, false, registeredNodeA, registeredNodeB)
Expect(ruleset.RegisteredFor(unregisteredNode)).Should(BeEmpty()) Expect(ruleset.RegisteredFor(unregisteredNode)).Should(BeEmpty())
Expect(ruleset.RegisteredFor(registeredNodeA)).Should(ContainElement(dummyIssueRule)) Expect(ruleset.RegisteredFor(registeredNodeA)).Should(ContainElement(dummyIssueRule))
Expect(ruleset.RegisteredFor(registeredNodeB)).Should(ContainElement(dummyIssueRule)) Expect(ruleset.RegisteredFor(registeredNodeB)).Should(ContainElement(dummyIssueRule))
Expect(ruleset.IsRuleSuppressed(dummyIssueRule.ID())).Should(BeFalse())
}) })
It("should not register a rule when no ast.Nodes are specified", func() { It("should not register a rule when no ast.Nodes are specified", func() {
ruleset.Register(dummyErrorRule) ruleset.Register(dummyErrorRule, false)
Expect(ruleset).Should(BeEmpty()) Expect(ruleset.Rules).Should(BeEmpty())
}) })
It("should be possible to retrieve a list of rules for a given node type", func() { It("should be possible to retrieve a list of rules for a given node type", func() {
registeredNode := (*ast.CallExpr)(nil) registeredNode := (*ast.CallExpr)(nil)
unregisteredNode := (*ast.AssignStmt)(nil) unregisteredNode := (*ast.AssignStmt)(nil)
ruleset.Register(dummyErrorRule, registeredNode) ruleset.Register(dummyErrorRule, false, registeredNode)
ruleset.Register(dummyIssueRule, registeredNode) ruleset.Register(dummyIssueRule, false, registeredNode)
Expect(ruleset.RegisteredFor(unregisteredNode)).Should(BeEmpty()) Expect(ruleset.RegisteredFor(unregisteredNode)).Should(BeEmpty())
Expect(ruleset.RegisteredFor(registeredNode)).Should(HaveLen(2)) Expect(ruleset.RegisteredFor(registeredNode)).Should(HaveLen(2))
Expect(ruleset.RegisteredFor(registeredNode)).Should(ContainElement(dummyErrorRule)) Expect(ruleset.RegisteredFor(registeredNode)).Should(ContainElement(dummyErrorRule))
Expect(ruleset.RegisteredFor(registeredNode)).Should(ContainElement(dummyIssueRule)) Expect(ruleset.RegisteredFor(registeredNode)).Should(ContainElement(dummyIssueRule))
}) })
It("should register a suppressed rule", func() {
registeredNode := (*ast.CallExpr)(nil)
unregisteredNode := (*ast.AssignStmt)(nil)
ruleset.Register(dummyIssueRule, true, registeredNode)
Expect(ruleset.RegisteredFor(registeredNode)).Should(ContainElement(dummyIssueRule))
Expect(ruleset.RegisteredFor(unregisteredNode)).Should(BeEmpty())
Expect(ruleset.IsRuleSuppressed(dummyIssueRule.ID())).Should(BeTrue())
})
}) })
}) })

View file

@ -24,16 +24,21 @@ type RuleDefinition struct {
Create gosec.RuleBuilder Create gosec.RuleBuilder
} }
// RuleList is a mapping of rule ID's to rule definitions // RuleList contains a mapping of rule ID's to rule definitions and a mapping
type RuleList map[string]RuleDefinition // of rule ID's to whether rules are suppressed.
type RuleList struct {
Rules map[string]RuleDefinition
RuleSuppressed map[string]bool
}
// Builders returns all the create methods for a given rule list // RulesInfo returns all the create methods and the rule suppressed map for a
func (rl RuleList) Builders() map[string]gosec.RuleBuilder { // given list
func (rl RuleList) RulesInfo() (map[string]gosec.RuleBuilder, map[string]bool) {
builders := make(map[string]gosec.RuleBuilder) builders := make(map[string]gosec.RuleBuilder)
for _, def := range rl { for _, def := range rl.Rules {
builders[def.ID] = def.Create builders[def.ID] = def.Create
} }
return builders return builders, rl.RuleSuppressed
} }
// RuleFilter can be used to include or exclude a rule depending on the return // RuleFilter can be used to include or exclude a rule depending on the return
@ -56,7 +61,7 @@ func NewRuleFilter(action bool, ruleIDs ...string) RuleFilter {
} }
// Generate the list of rules to use // Generate the list of rules to use
func Generate(filters ...RuleFilter) RuleList { func Generate(trackSuppressions bool, filters ...RuleFilter) RuleList {
rules := []RuleDefinition{ rules := []RuleDefinition{
// misc // misc
{"G101", "Look for hardcoded credentials", NewHardcodedCredentials}, {"G101", "Look for hardcoded credentials", NewHardcodedCredentials},
@ -102,15 +107,20 @@ func Generate(filters ...RuleFilter) RuleList {
} }
ruleMap := make(map[string]RuleDefinition) ruleMap := make(map[string]RuleDefinition)
ruleSuppressedMap := make(map[string]bool)
RULES: RULES:
for _, rule := range rules { for _, rule := range rules {
ruleSuppressedMap[rule.ID] = false
for _, filter := range filters { for _, filter := range filters {
if filter(rule.ID) { if filter(rule.ID) {
continue RULES ruleSuppressedMap[rule.ID] = true
if !trackSuppressions {
continue RULES
}
} }
} }
ruleMap[rule.ID] = rule ruleMap[rule.ID] = rule
} }
return ruleMap return RuleList{ruleMap, ruleSuppressedMap}
} }

View file

@ -24,12 +24,12 @@ var _ = Describe("gosec rules", func() {
BeforeEach(func() { BeforeEach(func() {
logger, _ = testutils.NewLogger() logger, _ = testutils.NewLogger()
config = gosec.NewConfig() config = gosec.NewConfig()
analyzer = gosec.NewAnalyzer(config, tests, false, logger) analyzer = gosec.NewAnalyzer(config, tests, false, false, logger)
runner = func(rule string, samples []testutils.CodeSample) { runner = func(rule string, samples []testutils.CodeSample) {
for n, sample := range samples { for n, sample := range samples {
analyzer.Reset() analyzer.Reset()
analyzer.SetConfig(sample.Config) analyzer.SetConfig(sample.Config)
analyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, rule)).Builders()) analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, rule)).RulesInfo())
pkg := testutils.NewTestPackage() pkg := testutils.NewTestPackage()
defer pkg.Close() defer pkg.Close()
for i, code := range sample.Code { for i, code := range sample.Code {