1
0
mirror of https://github.com/securego/gosec.git synced 2025-07-03 00:27:05 +02:00

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
15 changed files with 448 additions and 127 deletions

View File

@ -43,6 +43,10 @@ const LoadMode = packages.NeedName |
packages.NeedTypesInfo |
packages.NeedSyntax
const externalSuppressionJustification = "Globally suppressed."
const aliasOfAllRules = "*"
var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`)
// 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
Config Config
Imports *ImportTracker
Ignores []map[string]bool
Ignores []map[string][]SuppressionInfo
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
// and invoke the correct checking rules as on each node as required.
type Analyzer struct {
ignoreNosec bool
ruleset RuleSet
context *Context
config Config
logger *log.Logger
issues []*Issue
stats *Metrics
errors map[string][]Error // keys are file paths; values are the golang errors in those files
tests bool
excludeGenerated bool
showIgnored bool
ignoreNosec bool
ruleset RuleSet
context *Context
config Config
logger *log.Logger
issues []*Issue
stats *Metrics
errors map[string][]Error // keys are file paths; values are the golang errors in those files
tests bool
excludeGenerated 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.
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
if enabled, err := conf.IsGlobalEnabled(Nosec); err == nil {
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)
}
return &Analyzer{
ignoreNosec: ignoreNoSec,
showIgnored: showIgnored,
ruleset: make(RuleSet),
context: &Context{},
config: conf,
logger: logger,
issues: make([]*Issue, 0, 16),
stats: &Metrics{},
errors: make(map[string][]Error),
tests: tests,
excludeGenerated: excludeGenerated,
ignoreNosec: ignoreNoSec,
showIgnored: showIgnored,
ruleset: NewRuleSet(),
context: &Context{},
config: conf,
logger: logger,
issues: make([]*Issue, 0, 16),
stats: &Metrics{},
errors: make(map[string][]Error),
tests: tests,
excludeGenerated: excludeGenerated,
trackSuppressions: trackSuppressions,
}
}
@ -125,10 +138,10 @@ func (gosec *Analyzer) Config() Config {
// LoadRules instantiates all the rules to be used when analyzing source
// 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 {
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
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 {
// 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 {
foundDefaultTag := strings.Contains(group.Text(), noSecDefaultTag)
foundAlternativeTag := strings.Contains(group.Text(), noSecAlternativeTag)
foundDefaultTag := strings.HasPrefix(group.Text(), noSecDefaultTag)
foundAlternativeTag := strings.HasPrefix(group.Text(), noSecAlternativeTag)
if foundDefaultTag || foundAlternativeTag {
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.
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.
if len(matches) == 0 {
return nil, true
suppression := SuppressionInfo{
Kind: "inSource",
Justification: justification,
}
// Find the rule IDs to ignore.
var ignores []string
ignores := make(map[string]SuppressionInfo)
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.
@ -346,31 +372,40 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
}
// Get any new rule exclusions.
ignoredRules, ignoreAll := gosec.ignore(n)
if ignoreAll {
return nil
}
ignoredRules := gosec.ignore(n)
// Now create the union of exclusions.
ignores := map[string]bool{}
ignores := map[string][]SuppressionInfo{}
if len(gosec.context.Ignores) > 0 {
for k, v := range gosec.context.Ignores[0] {
ignores[k] = v
}
}
for _, v := range ignoredRules {
ignores[v] = true
for ruleID, suppression := range ignoredRules {
ignores[ruleID] = append(ignores[ruleID], suppression)
}
// 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
gosec.context.Imports.TrackImport(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)
if err != nil {
@ -385,7 +420,10 @@ func (gosec *Analyzer) Visit(n ast.Node) ast.Visitor {
if !ignored || !gosec.showIgnored {
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)
}
}