Ensure the ignores are parsed before analysing the package

In addition this handles the ignores for multi-line issues

Signed-off-by: Cosmin Cojocar <gcojocar@adobe.com>
This commit is contained in:
Cosmin Cojocar 2023-10-18 11:31:54 +02:00 committed by Cosmin Cojocar
parent 7846db034c
commit d8a6d358dc
2 changed files with 144 additions and 31 deletions

View file

@ -57,10 +57,78 @@ const aliasOfAllRules = "*"
var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
// ignoreLocation keeps the location of an ignored rule
type ignoreLocation struct {
file string
line string
type ignore struct {
start int
end int
suppressions map[string][]issue.SuppressionInfo
}
type ignores map[string][]ignore
func newIgnores() ignores {
return make(map[string][]ignore)
}
func (i ignores) parseLine(line string) (int, int) {
parts := strings.Split(line, "-")
start, err := strconv.Atoi(parts[0])
if err != nil {
start = 0
}
end := start
if len(parts) > 1 {
if e, err := strconv.Atoi(parts[1]); err == nil {
end = e
}
}
return start, end
}
func (i ignores) add(file string, line string, suppressions map[string]issue.SuppressionInfo) {
is := []ignore{}
if _, ok := i[file]; ok {
is = i[file]
}
found := false
start, end := i.parseLine(line)
for _, ig := range is {
if ig.start <= start && ig.end >= end {
found = true
for r, s := range suppressions {
ss, ok := ig.suppressions[r]
if !ok {
ss = []issue.SuppressionInfo{}
}
ss = append(ss, s)
ig.suppressions[r] = ss
}
break
}
}
if !found {
ig := ignore{
start: start,
end: end,
suppressions: map[string][]issue.SuppressionInfo{},
}
for r, s := range suppressions {
ig.suppressions[r] = []issue.SuppressionInfo{s}
}
is = append(is, ig)
}
i[file] = is
}
func (i ignores) get(file string, line string) map[string][]issue.SuppressionInfo {
start, end := i.parseLine(line)
if is, ok := i[file]; ok {
for _, i := range is {
if i.start <= start && i.end >= end {
return i.suppressions
}
}
}
return map[string][]issue.SuppressionInfo{}
}
// The Context is populated with data parsed from the source code as it is scanned.
@ -75,7 +143,7 @@ type Context struct {
Root *ast.File
Imports *ImportTracker
Config Config
Ignores map[ignoreLocation]map[string][]issue.SuppressionInfo
Ignores ignores
PassedValues map[string]interface{}
}
@ -318,6 +386,8 @@ func (gosec *Analyzer) CheckRules(pkg *packages.Package) {
gosec.context.PkgFiles = pkg.Syntax
gosec.context.Imports = NewImportTracker()
gosec.context.PassedValues = make(map[string]interface{})
gosec.context.Ignores = newIgnores()
gosec.updateIgnores()
ast.Walk(gosec, file)
gosec.stats.NumFiles++
gosec.stats.NumLines += pkg.Fset.File(file.Pos()).LineCount()
@ -527,9 +597,6 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo {
// Visit runs the gosec visitor logic over an AST created by parsing go code.
// Rule methods added with AddRule will be invoked as necessary.
func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
// Update any potentially ignored rules at the node location
gosec.updateIgnoredRules(n)
// Using ast.File instead of ast.ImportSpec, so that we can track all imports at once.
switch i := n.(type) {
case *ast.File:
@ -548,40 +615,33 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
return gosec
}
func (gosec *Analyzer) updateIgnoredRules(n ast.Node) {
func (gosec *Analyzer) updateIgnores() {
for n := range gosec.context.Comments {
gosec.updateIgnoredRulesForNode(n)
}
}
func (gosec *Analyzer) updateIgnoredRulesForNode(n ast.Node) {
ignoredRules := gosec.ignore(n)
if len(ignoredRules) > 0 {
if gosec.context.Ignores == nil {
gosec.context.Ignores = make(map[ignoreLocation]map[string][]issue.SuppressionInfo)
gosec.context.Ignores = newIgnores()
}
line := issue.GetLine(gosec.context.FileSet.File(n.Pos()), n)
ignoreLocation := ignoreLocation{
file: gosec.context.FileSet.File(n.Pos()).Name(),
line: line,
}
current, ok := gosec.context.Ignores[ignoreLocation]
if !ok {
current = map[string][]issue.SuppressionInfo{}
}
for r, s := range ignoredRules {
if current[r] == nil {
current[r] = []issue.SuppressionInfo{}
}
current[r] = append(current[r], s)
}
gosec.context.Ignores[ignoreLocation] = current
gosec.context.Ignores.add(
gosec.context.FileSet.File(n.Pos()).Name(),
line,
ignoredRules,
)
}
}
func (gosec *Analyzer) getSuppressionsAtLineInFile(file string, line string, id string) ([]issue.SuppressionInfo, bool) {
ignores, ok := gosec.context.Ignores[ignoreLocation{file: file, line: line}]
if !ok {
ignores = make(map[string][]issue.SuppressionInfo)
}
ignoredRules := gosec.context.Ignores.get(file, line)
// Check if the rule was specifically suppressed at this location.
generalSuppressions, generalIgnored := ignores[aliasOfAllRules]
ruleSuppressions, ruleIgnored := ignores[id]
generalSuppressions, generalIgnored := ignoredRules[aliasOfAllRules]
ruleSuppressions, ruleIgnored := ignoredRules[id]
ignored := generalIgnored || ruleIgnored
suppressions := append(generalSuppressions, ruleSuppressions...)

View file

@ -798,5 +798,58 @@ var _ = Describe("Analyzer", func() {
Expect(issues).Should(HaveLen(sample.Errors))
Expect(issues[0].Suppressions).To(HaveLen(2))
})
It("should not report an error if the violation is suppressed on a struct filed", func() {
sample := testutils.SampleCodeG402[0]
source := sample.Code[0]
analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G402")).RulesInfo())
nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close()
nosecSource := strings.Replace(source,
"TLSClientConfig: &tls.Config{InsecureSkipVerify: true}",
"TLSClientConfig: &tls.Config{InsecureSkipVerify: true} // #nosec G402", 1)
nosecPackage.AddFile("tls.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(sample.Errors))
Expect(issues[0].Suppressions).To(HaveLen(1))
Expect(issues[0].Suppressions[0].Kind).To(Equal("inSource"))
})
It("should not report an error if the violation is suppressed on multi-lien issue", func() {
source := `
package main
import (
"fmt"
)
const TokenLabel = `
source += "`" + `
f62e5bcda4fae4f82370da0c6f20697b8f8447ef
` + "`" + "//#nosec G101 -- false positive, this is not a private data" + `
func main() {
fmt.Printf("Label: %s ", TokenLabel)
}
`
analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G101")).RulesInfo())
nosecPackage := testutils.NewTestPackage()
defer nosecPackage.Close()
nosecPackage.AddFile("pwd.go", source)
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("false positive, this is not a private data"))
})
})
})