From 62db81342ea288f774b901eca957e0df5d693f0d Mon Sep 17 00:00:00 2001 From: Marc Brugger Date: Wed, 4 Aug 2021 17:33:20 +0200 Subject: [PATCH] Allow excluding generated files --- README.md | 14 ++++++++++- analyzer.go | 58 ++++++++++++++++++++++++++++++--------------- analyzer_test.go | 52 ++++++++++++++++++++++++++++++++++++---- cmd/gosec/main.go | 5 +++- rules/rules_test.go | 2 +- 5 files changed, 104 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index e69e416..99813b8 100644 --- a/README.md +++ b/README.md @@ -236,7 +236,6 @@ gosec will ignore test files across all packages and any dependencies in your ve The scanning of test files can be enabled with the following flag: ```bash - gosec -tests ./... ``` @@ -246,6 +245,19 @@ Also additional folders can be excluded as follows: gosec -exclude-dir=rules -exclude-dir=cmd ./... ``` +### Excluding generated files + +gosec can ignore generated go files with default generated code comment. + +``` +// Code generated by some generator DO NOT EDIT. +``` + +```bash +gosec -exclude-generated ./... +``` + + ### 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, diff --git a/analyzer.go b/analyzer.go index f669d5a..c8dfd6d 100644 --- a/analyzer.go +++ b/analyzer.go @@ -43,6 +43,8 @@ const LoadMode = packages.NeedName | packages.NeedTypesInfo | packages.NeedSyntax +var generatedCodePattern = regexp.MustCompile(`^// Code generated .* DO NOT EDIT\.$`) + // The Context is populated with data parsed from the source code as it is scanned. // It is passed through to all rule functions as they are called. Rules may use // this data in conjunction withe the encountered AST node. @@ -70,19 +72,20 @@ 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 + 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 } // NewAnalyzer builds a new analyzer. -func NewAnalyzer(conf Config, tests bool, logger *log.Logger) *Analyzer { +func NewAnalyzer(conf Config, tests bool, excludeGenerated bool, logger *log.Logger) *Analyzer { ignoreNoSec := false if enabled, err := conf.IsGlobalEnabled(Nosec); err == nil { ignoreNoSec = enabled @@ -91,15 +94,16 @@ func NewAnalyzer(conf Config, tests bool, logger *log.Logger) *Analyzer { logger = log.New(os.Stderr, "[gosec]", log.LstdFlags) } return &Analyzer{ - ignoreNosec: ignoreNoSec, - ruleset: make(RuleSet), - context: &Context{}, - config: conf, - logger: logger, - issues: make([]*Issue, 0, 16), - stats: &Metrics{}, - errors: make(map[string][]Error), - tests: tests, + ignoreNosec: ignoreNoSec, + 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, } } @@ -202,6 +206,11 @@ func (gosec *Analyzer) Check(pkg *packages.Package) { if filepath.Ext(checkedFile) != ".go" { continue } + if gosec.excludeGenerated && isGeneratedFile(file) { + gosec.logger.Println("Ignoring generated file:", checkedFile) + continue + } + gosec.logger.Println("Checking file:", checkedFile) gosec.context.FileSet = pkg.Fset gosec.context.Config = gosec.config @@ -219,6 +228,17 @@ func (gosec *Analyzer) Check(pkg *packages.Package) { } } +func isGeneratedFile(file *ast.File) bool { + for _, comment := range file.Comments { + for _, row := range comment.List { + if generatedCodePattern.MatchString(row.Text) { + return true + } + } + } + return false +} + // ParseErrors parses the errors from given package func (gosec *Analyzer) ParseErrors(pkg *packages.Package) error { if len(pkg.Errors) == 0 { diff --git a/analyzer_test.go b/analyzer_test.go index 6eeb87b..8752585 100644 --- a/analyzer_test.go +++ b/analyzer_test.go @@ -25,7 +25,7 @@ var _ = Describe("Analyzer", func() { ) BeforeEach(func() { logger, _ = testutils.NewLogger() - analyzer = gosec.NewAnalyzer(nil, tests, logger) + analyzer = gosec.NewAnalyzer(nil, tests, false, logger) }) Context("when processing a package", func() { @@ -246,7 +246,7 @@ var _ = Describe("Analyzer", func() { // overwrite nosec option nosecIgnoreConfig := gosec.NewConfig() nosecIgnoreConfig.SetGlobal(gosec.Nosec, "true") - customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, logger) + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, logger) customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) nosecPackage := testutils.NewTestPackage() @@ -269,7 +269,7 @@ var _ = Describe("Analyzer", func() { // overwrite nosec option nosecIgnoreConfig := gosec.NewConfig() nosecIgnoreConfig.SetGlobal(gosec.NoSecAlternative, "#falsePositive") - customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, logger) + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, logger) customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) nosecPackage := testutils.NewTestPackage() @@ -292,7 +292,7 @@ var _ = Describe("Analyzer", func() { // overwrite nosec option nosecIgnoreConfig := gosec.NewConfig() nosecIgnoreConfig.SetGlobal(gosec.NoSecAlternative, "#falsePositive") - customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, logger) + customAnalyzer := gosec.NewAnalyzer(nosecIgnoreConfig, tests, false, logger) customAnalyzer.LoadRules(rules.Generate(rules.NewRuleFilter(false, "G401")).Builders()) nosecPackage := testutils.NewTestPackage() @@ -308,7 +308,7 @@ var _ = Describe("Analyzer", func() { }) It("should be able to analyze Go test package", func() { - customAnalyzer := gosec.NewAnalyzer(nil, true, logger) + customAnalyzer := gosec.NewAnalyzer(nil, true, false, logger) customAnalyzer.LoadRules(rules.Generate().Builders()) pkg := testutils.NewTestPackage() defer pkg.Close() @@ -332,6 +332,48 @@ var _ = Describe("Analyzer", func() { issues, _, _ := customAnalyzer.Report() Expect(issues).Should(HaveLen(1)) }) + It("should be able to scan generated files if NOT excluded", func() { + customAnalyzer := gosec.NewAnalyzer(nil, true, false, logger) + customAnalyzer.LoadRules(rules.Generate().Builders()) + pkg := testutils.NewTestPackage() + defer pkg.Close() + pkg.AddFile("foo.go", ` + package foo + // Code generated some-generator DO NOT EDIT. + func test() error { + return nil + } + func TestFoo(t *testing.T){ + test() + }`) + err := pkg.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = customAnalyzer.Process(buildTags, pkg.Path) + Expect(err).ShouldNot(HaveOccurred()) + issues, _, _ := customAnalyzer.Report() + Expect(issues).Should(HaveLen(1)) + }) + It("should be able to skip generated files if excluded", func() { + customAnalyzer := gosec.NewAnalyzer(nil, true, true, logger) + customAnalyzer.LoadRules(rules.Generate().Builders()) + pkg := testutils.NewTestPackage() + defer pkg.Close() + pkg.AddFile("foo.go", ` + package foo + // Code generated some-generator DO NOT EDIT. + func test() error { + return nil + } + func TestFoo(t *testing.T){ + test() + }`) + err := pkg.Build() + Expect(err).ShouldNot(HaveOccurred()) + err = customAnalyzer.Process(buildTags, pkg.Path) + Expect(err).ShouldNot(HaveOccurred()) + issues, _, _ := customAnalyzer.Report() + Expect(issues).Should(HaveLen(0)) + }) }) It("should be able to analyze Cgo files", func() { analyzer.LoadRules(rules.Generate().Builders()) diff --git a/cmd/gosec/main.go b/cmd/gosec/main.go index fa7a7c8..3c93c12 100644 --- a/cmd/gosec/main.go +++ b/cmd/gosec/main.go @@ -93,6 +93,9 @@ var ( // rules to explicitly exclude flagRulesExclude = flag.String("exclude", "", "Comma separated list of rules IDs to exclude. (see rule list)") + // rules to explicitly exclude + flagExcludeGenerated = flag.Bool("exclude-generated", false, "Exclude generated files") + // log to file or stderr flagLogfile = flag.String("log", "", "Log messages to file rather than stderr") @@ -335,7 +338,7 @@ func main() { } // Create the analyzer - analyzer := gosec.NewAnalyzer(config, *flagScanTests, logger) + analyzer := gosec.NewAnalyzer(config, *flagScanTests, *flagExcludeGenerated, logger) analyzer.LoadRules(ruleDefinitions.Builders()) excludedDirs := gosec.ExcludedDirsRegExp(flagDirsExclude) diff --git a/rules/rules_test.go b/rules/rules_test.go index d13802b..7032b3f 100644 --- a/rules/rules_test.go +++ b/rules/rules_test.go @@ -25,7 +25,7 @@ var _ = Describe("gosec rules", func() { BeforeEach(func() { logger, _ = testutils.NewLogger() config = gosec.NewConfig() - analyzer = gosec.NewAnalyzer(config, tests, logger) + analyzer = gosec.NewAnalyzer(config, tests, false, logger) runner = func(rule string, samples []testutils.CodeSample) { for n, sample := range samples { analyzer.Reset()