Skip to content

Commit 0ec6cd9

Browse files
committed
Refactor how ignored issues are tracked
Track ignored issues using file location instead of a AST node. There are issues linked to a different AST node than the original node used to start the scan. Signed-off-by: Cosmin Cojocar <gcojocar@adobe.com>
1 parent f338a98 commit 0ec6cd9

File tree

5 files changed

+56
-65
lines changed

5 files changed

+56
-65
lines changed

analyzer.go

+40-36
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ const aliasOfAllRules = "*"
5757

5858
var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
5959

60+
// ignoreLocation keeps the location of an ignored rule
61+
type ignoreLocation struct {
62+
file string
63+
line string
64+
}
65+
6066
// The Context is populated with data parsed from the source code as it is scanned.
6167
// It is passed through to all rule functions as they are called. Rules may use
6268
// this data in conjunction with the encountered AST node.
@@ -69,7 +75,7 @@ type Context struct {
6975
Root *ast.File
7076
Imports *ImportTracker
7177
Config Config
72-
Ignores []map[string][]issue.SuppressionInfo
78+
Ignores map[ignoreLocation]map[string][]issue.SuppressionInfo
7379
PassedValues map[string]interface{}
7480
}
7581

@@ -360,7 +366,7 @@ func (gosec *Analyzer) CheckAnalyzers(pkg *packages.Package) {
360366
if result != nil {
361367
if passIssues, ok := result.([]*issue.Issue); ok {
362368
for _, iss := range passIssues {
363-
gosec.updateIssues(iss, false, []issue.SuppressionInfo{})
369+
gosec.updateIssues(iss)
364370
}
365371
}
366372
}
@@ -521,10 +527,8 @@ func (gosec *Analyzer) ignore(n ast.Node) map[string]issue.SuppressionInfo {
521527
// Visit runs the gosec visitor logic over an AST created by parsing go code.
522528
// Rule methods added with AddRule will be invoked as necessary.
523529
func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
524-
ignores, ok := gosec.updateIgnoredRules(n)
525-
if !ok {
526-
return gosec
527-
}
530+
// Update any potentially ignored rules at the node location
531+
gosec.updateIgnoredRules(n)
528532

529533
// Using ast.File instead of ast.ImportSpec, so that we can track all imports at once.
530534
switch i := n.(type) {
@@ -533,56 +537,55 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
533537
}
534538

535539
for _, rule := range gosec.ruleset.RegisteredFor(n) {
536-
suppressions, ignored := gosec.updateSuppressions(rule.ID(), ignores)
537540
issue, err := rule.Match(n, gosec.context)
538541
if err != nil {
539542
file, line := GetLocation(n, gosec.context)
540543
file = path.Base(file)
541544
gosec.logger.Printf("Rule error: %v => %s (%s:%d)\n", reflect.TypeOf(rule), err, file, line)
542545
}
543-
gosec.updateIssues(issue, ignored, suppressions)
546+
gosec.updateIssues(issue)
544547
}
545548
return gosec
546549
}
547550

548-
func (gosec *Analyzer) updateIgnoredRules(n ast.Node) (map[string][]issue.SuppressionInfo, bool) {
549-
if n == nil {
550-
if len(gosec.context.Ignores) > 0 {
551-
gosec.context.Ignores = gosec.context.Ignores[1:]
552-
}
553-
return nil, false
554-
}
555-
// Get any new rule exclusions.
551+
func (gosec *Analyzer) updateIgnoredRules(n ast.Node) {
556552
ignoredRules := gosec.ignore(n)
557-
558-
// Now create the union of exclusions.
559-
ignores := map[string][]issue.SuppressionInfo{}
560-
if len(gosec.context.Ignores) > 0 {
561-
for k, v := range gosec.context.Ignores[0] {
562-
ignores[k] = v
553+
if len(ignoredRules) > 0 {
554+
if gosec.context.Ignores == nil {
555+
gosec.context.Ignores = make(map[ignoreLocation]map[string][]issue.SuppressionInfo)
556+
}
557+
line := issue.GetLine(gosec.context.FileSet.File(n.Pos()), n)
558+
ignoreLocation := ignoreLocation{
559+
file: gosec.context.FileSet.File(n.Pos()).Name(),
560+
line: line,
561+
}
562+
current, ok := gosec.context.Ignores[ignoreLocation]
563+
if !ok {
564+
current = map[string][]issue.SuppressionInfo{}
563565
}
566+
for r, s := range ignoredRules {
567+
if current[r] == nil {
568+
current[r] = []issue.SuppressionInfo{}
569+
}
570+
current[r] = append(current[r], s)
571+
}
572+
gosec.context.Ignores[ignoreLocation] = current
564573
}
574+
}
565575

566-
for ruleID, suppression := range ignoredRules {
567-
ignores[ruleID] = append(ignores[ruleID], suppression)
576+
func (gosec *Analyzer) getSuppressionsAtLineInFile(file string, line string, id string) ([]issue.SuppressionInfo, bool) {
577+
ignores, ok := gosec.context.Ignores[ignoreLocation{file: file, line: line}]
578+
if !ok {
579+
ignores = make(map[string][]issue.SuppressionInfo)
568580
}
569581

570-
// Push the new set onto the stack.
571-
gosec.context.Ignores = append([]map[string][]issue.SuppressionInfo{ignores}, gosec.context.Ignores...)
572-
573-
return ignores, true
574-
}
575-
576-
func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]issue.SuppressionInfo) ([]issue.SuppressionInfo, bool) {
577-
// Check if all rules are ignored.
582+
// Check if the rule was specifically suppressed at this location.
578583
generalSuppressions, generalIgnored := ignores[aliasOfAllRules]
579-
// Check if the specific rule is ignored
580584
ruleSuppressions, ruleIgnored := ignores[id]
581-
582585
ignored := generalIgnored || ruleIgnored
583586
suppressions := append(generalSuppressions, ruleSuppressions...)
584587

585-
// Track external suppressions.
588+
// Track external suppressions of this rule.
586589
if gosec.ruleset.IsRuleSuppressed(id) {
587590
ignored = true
588591
suppressions = append(suppressions, issue.SuppressionInfo{
@@ -593,8 +596,9 @@ func (gosec *Analyzer) updateSuppressions(id string, ignores map[string][]issue.
593596
return suppressions, ignored
594597
}
595598

596-
func (gosec *Analyzer) updateIssues(issue *issue.Issue, ignored bool, suppressions []issue.SuppressionInfo) {
599+
func (gosec *Analyzer) updateIssues(issue *issue.Issue) {
597600
if issue != nil {
601+
suppressions, ignored := gosec.getSuppressionsAtLineInFile(issue.File, issue.Line, issue.RuleID)
598602
if gosec.showIgnored {
599603
issue.NoSec = ignored
600604
}

analyzer_test.go

+1-20
Original file line numberDiff line numberDiff line change
@@ -743,25 +743,6 @@ var _ = Describe("Analyzer", func() {
743743
Expect(issues[0].Suppressions[0].Justification).To(Equal(""))
744744
})
745745

746-
It("should track multiple suppressions if the violation is suppressed by both #nosec and #nosec RuleList", func() {
747-
sample := testutils.SampleCodeG101[0]
748-
source := sample.Code[0]
749-
analyzer.LoadRules(rules.Generate(false, rules.NewRuleFilter(false, "G101")).RulesInfo())
750-
751-
nosecPackage := testutils.NewTestPackage()
752-
defer nosecPackage.Close()
753-
nosecSource := strings.Replace(source, "}", "} //#nosec G101 -- Justification", 1)
754-
nosecSource = strings.Replace(nosecSource, "func", "//#nosec\nfunc", 1)
755-
nosecPackage.AddFile("pwd.go", nosecSource)
756-
err := nosecPackage.Build()
757-
Expect(err).ShouldNot(HaveOccurred())
758-
err = analyzer.Process(buildTags, nosecPackage.Path)
759-
Expect(err).ShouldNot(HaveOccurred())
760-
issues, _, _ := analyzer.Report()
761-
Expect(issues).To(HaveLen(sample.Errors))
762-
Expect(issues[0].Suppressions).To(HaveLen(2))
763-
})
764-
765746
It("should not report an error if the rule is not included", func() {
766747
sample := testutils.SampleCodeG101[0]
767748
source := sample.Code[0]
@@ -807,7 +788,7 @@ var _ = Describe("Analyzer", func() {
807788

808789
nosecPackage := testutils.NewTestPackage()
809790
defer nosecPackage.Close()
810-
nosecSource := strings.Replace(source, "}", "} //#nosec G101 -- Justification", 1)
791+
nosecSource := strings.Replace(source, "password := \"f62e5bcda4fae4f82370da0c6f20697b8f8447ef\"", "password := \"f62e5bcda4fae4f82370da0c6f20697b8f8447ef\" //#nosec G101 -- Justification", 1)
811792
nosecPackage.AddFile("pwd.go", nosecSource)
812793
err := nosecPackage.Build()
813794
Expect(err).ShouldNot(HaveOccurred())

cmd/tlsconfig/tlsconfig.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func main() {
187187
}
188188

189189
outputPath := filepath.Join(dir, *outputFile)
190-
if err := os.WriteFile(outputPath, src, 0o644); err != nil {
190+
if err := os.WriteFile(outputPath, src, 0o644); err != nil /*#nosec G306*/ {
191191
log.Fatalf("Writing output: %s", err)
192-
} //#nosec G306
192+
}
193193
}

issue/issue.go

+11-5
Original file line numberDiff line numberDiff line change
@@ -178,11 +178,7 @@ func codeSnippetEndLine(node ast.Node, fobj *token.File) int64 {
178178
// New creates a new Issue
179179
func New(fobj *token.File, node ast.Node, ruleID, desc string, severity, confidence Score) *Issue {
180180
name := fobj.Name()
181-
start, end := fobj.Line(node.Pos()), fobj.Line(node.End())
182-
line := strconv.Itoa(start)
183-
if start != end {
184-
line = fmt.Sprintf("%d-%d", start, end)
185-
}
181+
line := GetLine(fobj, node)
186182
col := strconv.Itoa(fobj.Position(node.Pos()).Column)
187183

188184
var code string
@@ -217,3 +213,13 @@ func (i *Issue) WithSuppressions(suppressions []SuppressionInfo) *Issue {
217213
i.Suppressions = suppressions
218214
return i
219215
}
216+
217+
// GetLine returns the line number of a given ast.Node
218+
func GetLine(fobj *token.File, node ast.Node) string {
219+
start, end := fobj.Line(node.Pos()), fobj.Line(node.End())
220+
line := strconv.Itoa(start)
221+
if start != end {
222+
line = fmt.Sprintf("%d-%d", start, end)
223+
}
224+
return line
225+
}

testutils/pkg.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -53,9 +53,9 @@ func (p *TestPackage) write() error {
5353
return nil
5454
}
5555
for filename, content := range p.Files {
56-
if e := os.WriteFile(filename, []byte(content), 0o644); e != nil {
56+
if e := os.WriteFile(filename, []byte(content), 0o644); e != nil /* #nosec G306 */ {
5757
return e
58-
} //#nosec G306
58+
}
5959
}
6060
p.onDisk = true
6161
return nil

0 commit comments

Comments
 (0)