diff --git a/go.mod b/go.mod index 654a1fd..203a9c8 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.14 require ( github.com/ZupIT/horusec-devkit v1.0.24 + github.com/bmatcuk/doublestar/v4 v4.2.0 github.com/panjf2000/ants/v2 v2.4.8 github.com/stretchr/testify v1.7.1 golang.org/x/sync v0.0.0-20210220032951-036812b2e83c diff --git a/go.sum b/go.sum index 44c10bc..c603350 100644 --- a/go.sum +++ b/go.sum @@ -58,6 +58,8 @@ github.com/auth0/go-jwt-middleware v1.0.1/go.mod h1:YSeUX3z6+TF2H+7padiEqNJ73Zy9 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bmatcuk/doublestar/v4 v4.2.0 h1:Qu+u9wR3Vd89LnlLMHvnZ5coJMWKQamqdz9/p5GNthA= +github.com/bmatcuk/doublestar/v4 v4.2.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= diff --git a/rule.go b/rule.go index 55df143..c1f82d8 100644 --- a/rule.go +++ b/rule.go @@ -26,6 +26,7 @@ type Metadata struct { Description string Severity string Confidence string + Filter string `default:"**"` CWEs []string CVEs []string Mitigation string diff --git a/text/rule.go b/text/rule.go index 8a69c6a..59139ea 100644 --- a/text/rule.go +++ b/text/rule.go @@ -21,6 +21,8 @@ import ( "os" "regexp" + "github.com/bmatcuk/doublestar/v4" + engine "github.com/ZupIT/horusec-engine" ) @@ -62,9 +64,9 @@ type Rule struct { // file with it. The text file contains all information needed to find the vulnerable code when the regular expressions // match. There's also a validation to ignore binary files func (r *Rule) Run(path string) ([]engine.Finding, error) { - content, err := r.getFileContent(path) - if err != nil { - return nil, err + content, err := r.getFilteredFileContent(path) + if content == nil || err != nil { + return nil, nil } if r.isBinary(content) { @@ -79,6 +81,20 @@ func (r *Rule) Run(path string) ([]engine.Finding, error) { return r.runByRuleType(textFile) } +func (r *Rule) getFilteredFileContent(path string) ([]byte, error) { + matched, _ := doublestar.Match(r.Filter, path) + if !matched { + return nil, nil + } + + content, err := r.getFileContent(path) + if err != nil { + return nil, err + } + + return content, nil +} + // getFileContent opens the file using the file path, reads and returns its contents as bytes. After all done closes // the file func (r *Rule) getFileContent(path string) ([]byte, error) { diff --git a/text/rule_test.go b/text/rule_test.go index 539396a..c336db6 100644 --- a/text/rule_test.go +++ b/text/rule_test.go @@ -27,6 +27,7 @@ func TestRun(t *testing.T) { name string filepath string matchType MatchType + filter string expressions []*regexp.Regexp expectedFindings int }{ @@ -34,6 +35,7 @@ func TestRun(t *testing.T) { name: "Should return 1 findings with match type AndMatch and go sample", filepath: filepath.Join("examples", "go", "example1", "api", "server.go"), matchType: AndMatch, + filter: "**/*.go", expressions: []*regexp.Regexp{ regexp.MustCompile(`Logger\.Fatal`), regexp.MustCompile(`echoInstance.Start`), @@ -44,6 +46,7 @@ func TestRun(t *testing.T) { name: "Should return 3 findings with match type OrMatch and go sample", filepath: filepath.Join("examples", "go", "example1", "api", "server.go"), matchType: OrMatch, + filter: "**/*.go", expressions: []*regexp.Regexp{ regexp.MustCompile(`echoInstance\.Use\(middleware`), }, @@ -53,6 +56,7 @@ func TestRun(t *testing.T) { name: "Should return 1 findings with match type NotMatch and go sample", filepath: filepath.Join("examples", "go", "example1", "api", "server.go"), matchType: NotMatch, + filter: "**/*.go", expressions: []*regexp.Regexp{ regexp.MustCompile(`should-not-match`), }, @@ -62,6 +66,7 @@ func TestRun(t *testing.T) { name: "Should return 0 findings with match type AndMatch and go sample", filepath: filepath.Join("examples", "go", "example1", "api", "server.go"), matchType: AndMatch, + filter: "**/*.go", expressions: []*regexp.Regexp{ regexp.MustCompile(`should-not-match`), regexp.MustCompile(`echoInstance.Start`), @@ -72,6 +77,7 @@ func TestRun(t *testing.T) { name: "Should return 0 findings with match type OrMatch and go sample", filepath: filepath.Join("examples", "go", "example1", "api", "server.go"), matchType: OrMatch, + filter: "**/*.go", expressions: []*regexp.Regexp{ regexp.MustCompile(`should-not-match`), }, @@ -81,6 +87,7 @@ func TestRun(t *testing.T) { name: "Should return 0 findings with match type NotMatch and go sample", filepath: filepath.Join("examples", "go", "example1", "api", "server.go"), matchType: NotMatch, + filter: "**/*.go", expressions: []*regexp.Regexp{ regexp.MustCompile(`Logger\.Fatal`), }, @@ -90,6 +97,7 @@ func TestRun(t *testing.T) { name: "Should return 1 findings with match type AndMatch and python sample", filepath: filepath.Join("examples", "python", "example1", "main.py"), matchType: AndMatch, + filter: "**/*.py", expressions: []*regexp.Regexp{ regexp.MustCompile(`secret =`), regexp.MustCompile(`password =`), @@ -101,6 +109,7 @@ func TestRun(t *testing.T) { name: "Should return 3 findings with match type OrMatch and python sample", filepath: filepath.Join("examples", "python", "example1", "main.py"), matchType: OrMatch, + filter: "**/*.py", expressions: []*regexp.Regexp{ regexp.MustCompile(`secret =`), regexp.MustCompile(`password =`), @@ -113,6 +122,7 @@ func TestRun(t *testing.T) { name: "Should return 1 findings with match type NotMatch and python sample", filepath: filepath.Join("examples", "python", "example1", "main.py"), matchType: NotMatch, + filter: "**/*.py", expressions: []*regexp.Regexp{ regexp.MustCompile(`should-not-match`), }, @@ -122,6 +132,7 @@ func TestRun(t *testing.T) { name: "Should return 0 findings with match type AndMatch and python sample", filepath: filepath.Join("examples", "python", "example1", "main.py"), matchType: AndMatch, + filter: "**/*.py", expressions: []*regexp.Regexp{ regexp.MustCompile(`secret =`), regexp.MustCompile(`password =`), @@ -133,6 +144,7 @@ func TestRun(t *testing.T) { name: "Should return 0 findings with match type OrMatch and python sample", filepath: filepath.Join("examples", "python", "example1", "main.py"), matchType: OrMatch, + filter: "**/*.py", expressions: []*regexp.Regexp{ regexp.MustCompile(`should-not-match`), regexp.MustCompile(`should-not-match`), @@ -143,11 +155,35 @@ func TestRun(t *testing.T) { name: "Should return 0 findings with match type NotMatch and python sample", filepath: filepath.Join("examples", "python", "example1", "main.py"), matchType: NotMatch, + filter: "**/*.py", expressions: []*regexp.Regexp{ regexp.MustCompile(`secret =`), }, expectedFindings: 0, }, + { + name: "Should return 0 findings with non existing filename", + filepath: filepath.Join("examples", "python", "example1", "main.py"), + matchType: NotMatch, + filter: "**/non-existing-file.py", + expressions: []*regexp.Regexp{ + regexp.MustCompile(`secret =`), + }, + expectedFindings: 0, + }, + { + name: "Should not return 3 findings with non existing filtered filename", + filepath: filepath.Join("examples", "python", "example1", "main.py"), + matchType: OrMatch, + filter: "**/*.go", + expressions: []*regexp.Regexp{ + regexp.MustCompile(`secret =`), + regexp.MustCompile(`password =`), + regexp.MustCompile(`command =`), + regexp.MustCompile(`print(secret)`), + }, + expectedFindings: 0, + }, } for index, testCase := range testCases { @@ -156,6 +192,7 @@ func TestRun(t *testing.T) { Type: testCase.matchType, Expressions: testCase.expressions, } + rule.Metadata.Filter = testCase.filter findings, err := rule.Run(testCase.filepath) assert.NoError(t, err)