mirror of
https://github.com/securego/gosec.git
synced 2024-12-24 11:35:52 +00:00
Add support for suppressing the findings
This commit is contained in:
parent
040327f7d7
commit
b45f95f6ad
15 changed files with 448 additions and 127 deletions
28
README.md
28
README.md
|
@ -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.
|
||||||
|
|
132
analyzer.go
132
analyzer.go
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
158
analyzer_test.go
158
analyzer_test.go
|
@ -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))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
27
issue.go
27
issue.go
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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":[{`))
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
|
@ -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
27
rule.go
|
@ -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]
|
||||||
|
}
|
||||||
|
|
20
rule_test.go
20
rule_test.go
|
@ -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())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -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}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue