diff --git a/new/detector/composition/java/.snapshots/TestScope--scope.yml b/new/detector/composition/java/.snapshots/TestScope--scope.yml new file mode 100644 index 000000000..a1b8d0b1e --- /dev/null +++ b/new/detector/composition/java/.snapshots/TestScope--scope.yml @@ -0,0 +1,74 @@ +high: + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 1 + filename: scope.java + parent_line_number: 1 + snippet: scopeCursor(request.getParameter("oops")) + fingerprint: bdbeee20feb34c6881d975716e2fe09f_0 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 5 + filename: scope.java + parent_line_number: 5 + snippet: scopeNested(request.getParameter("oops")) + fingerprint: bdbeee20feb34c6881d975716e2fe09f_1 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 6 + filename: scope.java + parent_line_number: 6 + snippet: 'scopeNested(x ? request.getParameter("oops") : y)' + fingerprint: bdbeee20feb34c6881d975716e2fe09f_2 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 7 + filename: scope.java + parent_line_number: 7 + snippet: 'scopeNested(request.getParameter("oops") ? x : y)' + fingerprint: bdbeee20feb34c6881d975716e2fe09f_3 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 9 + filename: scope.java + parent_line_number: 9 + snippet: scopeResult(request.getParameter("oops")) + fingerprint: bdbeee20feb34c6881d975716e2fe09f_4 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 10 + filename: scope.java + parent_line_number: 10 + snippet: 'scopeResult(x ? request.getParameter("oops") : y)' + fingerprint: bdbeee20feb34c6881d975716e2fe09f_5 + diff --git a/new/detector/composition/java/java.go b/new/detector/composition/java/java.go index c10ab7d4a..a82e7cea8 100644 --- a/new/detector/composition/java/java.go +++ b/new/detector/composition/java/java.go @@ -193,7 +193,13 @@ func (composition *Composition) DetectFromFileWithTypes(file *file.FileInfo, det var result []*detectortypes.Detection for _, detectorType := range detectorTypes { rule := composition.rules[detectorType] - detections, err := evaluator.ForTree(tree.RootNode(), detectorType, rule.SanitizerRuleID, false) + detections, err := evaluator.Evaluate( + tree.RootNode(), + detectorType, + rule.SanitizerRuleID, + settings.NESTED_SCOPE, + false, + ) if err != nil { return nil, err } diff --git a/new/detector/composition/java/java_test.go b/new/detector/composition/java/java_test.go index 01ae72e0d..28fef7446 100644 --- a/new/detector/composition/java/java_test.go +++ b/new/detector/composition/java/java_test.go @@ -10,7 +10,15 @@ import ( //go:embed testdata/logger.yml var loggerRule []byte +//go:embed testdata/scope_rule.yml +var scopeRule []byte + func TestFlow(t *testing.T) { t.Parallel() - testhelper.GetRunner(t, loggerRule, "Javascript").RunTest(t, "./testdata/testcases/flow", ".snapshots/flow/") + testhelper.GetRunner(t, loggerRule, "Java").RunTest(t, "./testdata/testcases/flow", ".snapshots/flow/") +} + +func TestScope(t *testing.T) { + t.Parallel() + testhelper.GetRunner(t, scopeRule, "Java").RunTest(t, "./testdata/scope", ".snapshots/") } diff --git a/new/detector/composition/java/testdata/scope/scope.java b/new/detector/composition/java/testdata/scope/scope.java new file mode 100644 index 000000000..63af410e7 --- /dev/null +++ b/new/detector/composition/java/testdata/scope/scope.java @@ -0,0 +1,11 @@ +scopeCursor(request.getParameter("oops")) +scopeCursor(x ? request.getParameter("ok") : y) +scopeCursor(request.getParameter("ok") ? x : y) + +scopeNested(request.getParameter("oops")) +scopeNested(x ? request.getParameter("oops") : y) +scopeNested(request.getParameter("oops") ? x : y) + +scopeResult(request.getParameter("oops")) +scopeResult(x ? request.getParameter("oops") : y) +scopeResult(request.getParameter("ok") ? x : y) diff --git a/new/detector/composition/java/testdata/scope_rule.yml b/new/detector/composition/java/testdata/scope_rule.yml new file mode 100644 index 000000000..aebafe7c5 --- /dev/null +++ b/new/detector/composition/java/testdata/scope_rule.yml @@ -0,0 +1,29 @@ +languages: + - java +patterns: + - pattern: scopeCursor($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: cursor + - pattern: scopeNested($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: nested + - pattern: scopeResult($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: result +auxiliary: + - id: scope_test_user_input + patterns: + - request.getParameter() +severity: high +metadata: + description: Test detection filter scopes + remediation_message: Test detection filter scopes + cwe_id: + - 42 + id: scope_test diff --git a/new/detector/composition/javascript/.snapshots/TestScope--scope.yml b/new/detector/composition/javascript/.snapshots/TestScope--scope.yml new file mode 100644 index 000000000..8e8bfb162 --- /dev/null +++ b/new/detector/composition/javascript/.snapshots/TestScope--scope.yml @@ -0,0 +1,74 @@ +high: + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 1 + filename: scope.js + parent_line_number: 1 + snippet: scopeCursor(req.params.oops) + fingerprint: 408407aa362e0520faf6b66c3d59bb8c_0 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 5 + filename: scope.js + parent_line_number: 5 + snippet: scopeNested(req.params.oops) + fingerprint: 408407aa362e0520faf6b66c3d59bb8c_1 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 6 + filename: scope.js + parent_line_number: 6 + snippet: 'scopeNested(x ? req.params.oops : y)' + fingerprint: 408407aa362e0520faf6b66c3d59bb8c_2 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 7 + filename: scope.js + parent_line_number: 7 + snippet: 'scopeNested(req.params.oops ? x : y)' + fingerprint: 408407aa362e0520faf6b66c3d59bb8c_3 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 9 + filename: scope.js + parent_line_number: 9 + snippet: scopeResult(req.params.oops) + fingerprint: 408407aa362e0520faf6b66c3d59bb8c_4 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 10 + filename: scope.js + parent_line_number: 10 + snippet: 'scopeResult(x ? req.params.oops : y)' + fingerprint: 408407aa362e0520faf6b66c3d59bb8c_5 + diff --git a/new/detector/composition/javascript/javascript.go b/new/detector/composition/javascript/javascript.go index 10513fe96..141cf603e 100644 --- a/new/detector/composition/javascript/javascript.go +++ b/new/detector/composition/javascript/javascript.go @@ -193,7 +193,13 @@ func (composition *Composition) DetectFromFileWithTypes(file *file.FileInfo, det var result []*detectortypes.Detection for _, detectorType := range detectorTypes { rule := composition.rules[detectorType] - detections, err := evaluator.ForTree(tree.RootNode(), detectorType, rule.SanitizerRuleID, false) + detections, err := evaluator.Evaluate( + tree.RootNode(), + detectorType, + rule.SanitizerRuleID, + settings.NESTED_SCOPE, + false, + ) if err != nil { return nil, err } diff --git a/new/detector/composition/javascript/javascript_test.go b/new/detector/composition/javascript/javascript_test.go index 47c760cbf..c526855e6 100644 --- a/new/detector/composition/javascript/javascript_test.go +++ b/new/detector/composition/javascript/javascript_test.go @@ -16,6 +16,9 @@ var datatypeRule []byte //go:embed testdata/deconstructing.yml var deconstructingRule []byte +//go:embed testdata/scope_rule.yml +var scopeRule []byte + func TestFlow(t *testing.T) { t.Parallel() testhelper.GetRunner(t, datatypeRule, "Javascript").RunTest(t, "./testdata/testcases/flow", ".snapshots/flow/") @@ -30,3 +33,8 @@ func TestString(t *testing.T) { t.Parallel() testhelper.GetRunner(t, insecureURLRule, "Javascript").RunTest(t, "./testdata/testcases/string", ".snapshots/string/") } + +func TestScope(t *testing.T) { + t.Parallel() + testhelper.GetRunner(t, scopeRule, "Javascript").RunTest(t, "./testdata/scope", ".snapshots/") +} diff --git a/new/detector/composition/javascript/testdata/scope/scope.js b/new/detector/composition/javascript/testdata/scope/scope.js new file mode 100644 index 000000000..e954fedea --- /dev/null +++ b/new/detector/composition/javascript/testdata/scope/scope.js @@ -0,0 +1,11 @@ +scopeCursor(req.params.oops) +scopeCursor(x ? req.params.ok : y) +scopeCursor(req.params.ok ? x : y) + +scopeNested(req.params.oops) +scopeNested(x ? req.params.oops : y) +scopeNested(req.params.oops ? x : y) + +scopeResult(req.params.oops) +scopeResult(x ? req.params.oops : y) +scopeResult(req.params.ok ? x : y) diff --git a/new/detector/composition/javascript/testdata/scope_rule.yml b/new/detector/composition/javascript/testdata/scope_rule.yml new file mode 100644 index 000000000..3f94bd732 --- /dev/null +++ b/new/detector/composition/javascript/testdata/scope_rule.yml @@ -0,0 +1,29 @@ +languages: + - javascript +patterns: + - pattern: scopeCursor($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: cursor + - pattern: scopeNested($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: nested + - pattern: scopeResult($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: result +auxiliary: + - id: scope_test_user_input + patterns: + - req.params.$<_> +severity: high +metadata: + description: Test detection filter scopes + remediation_message: Test detection filter scopes + cwe_id: + - 42 + id: scope_test diff --git a/new/detector/composition/ruby/.snapshots/TestScope--scope.yml b/new/detector/composition/ruby/.snapshots/TestScope--scope.yml new file mode 100644 index 000000000..d21d13976 --- /dev/null +++ b/new/detector/composition/ruby/.snapshots/TestScope--scope.yml @@ -0,0 +1,74 @@ +high: + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 1 + filename: scope.rb + parent_line_number: 1 + snippet: scope_cursor(params[:oops]) + fingerprint: 23e17866f80f43957a84e824da9ce255_0 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 5 + filename: scope.rb + parent_line_number: 5 + snippet: scope_nested(params[:oops]) + fingerprint: 23e17866f80f43957a84e824da9ce255_1 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 6 + filename: scope.rb + parent_line_number: 6 + snippet: 'scope_nested(x ? params[:oops] : y)' + fingerprint: 23e17866f80f43957a84e824da9ce255_2 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 7 + filename: scope.rb + parent_line_number: 7 + snippet: 'scope_nested(params[:oops] ? x : y)' + fingerprint: 23e17866f80f43957a84e824da9ce255_3 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 9 + filename: scope.rb + parent_line_number: 9 + snippet: scope_result(params[:oops]) + fingerprint: 23e17866f80f43957a84e824da9ce255_4 + - rule: + cwe_ids: + - "42" + id: scope_test + title: Test detection filter scopes + description: Test detection filter scopes + documentation_url: "" + line_number: 10 + filename: scope.rb + parent_line_number: 10 + snippet: 'scope_result(x ? params[:oops] : y)' + fingerprint: 23e17866f80f43957a84e824da9ce255_5 + diff --git a/new/detector/composition/ruby/ruby.go b/new/detector/composition/ruby/ruby.go index 2b07ae361..a8ad67893 100644 --- a/new/detector/composition/ruby/ruby.go +++ b/new/detector/composition/ruby/ruby.go @@ -192,7 +192,13 @@ func (composition *Composition) DetectFromFileWithTypes(file *file.FileInfo, det var result []*detectortypes.Detection for _, detectorType := range detectorTypes { rule := composition.rules[detectorType] - detections, err := evaluator.ForTree(tree.RootNode(), detectorType, rule.SanitizerRuleID, false) + detections, err := evaluator.Evaluate( + tree.RootNode(), + detectorType, + rule.SanitizerRuleID, + settings.NESTED_SCOPE, + false, + ) if err != nil { return nil, err } diff --git a/new/detector/composition/ruby/ruby_test.go b/new/detector/composition/ruby/ruby_test.go index 058c1e367..433a51584 100644 --- a/new/detector/composition/ruby/ruby_test.go +++ b/new/detector/composition/ruby/ruby_test.go @@ -10,7 +10,15 @@ import ( //go:embed testdata/rule.yml var loggerRule []byte +//go:embed testdata/scope_rule.yml +var scopeRule []byte + func TestRuby(t *testing.T) { t.Parallel() testhelper.GetRunner(t, loggerRule, "Ruby").RunTest(t, "./testdata/testcases", ".snapshots/") } + +func TestScope(t *testing.T) { + t.Parallel() + testhelper.GetRunner(t, scopeRule, "Ruby").RunTest(t, "./testdata/scope", ".snapshots/") +} diff --git a/new/detector/composition/ruby/testdata/scope/scope.rb b/new/detector/composition/ruby/testdata/scope/scope.rb new file mode 100644 index 000000000..43932cac0 --- /dev/null +++ b/new/detector/composition/ruby/testdata/scope/scope.rb @@ -0,0 +1,11 @@ +scope_cursor(params[:oops]) +scope_cursor(x ? params[:ok] : y) +scope_cursor(params[:ok] ? x : y) + +scope_nested(params[:oops]) +scope_nested(x ? params[:oops] : y) +scope_nested(params[:oops] ? x : y) + +scope_result(params[:oops]) +scope_result(x ? params[:oops] : y) +scope_result(params[:ok] ? x : y) diff --git a/new/detector/composition/ruby/testdata/scope_rule.yml b/new/detector/composition/ruby/testdata/scope_rule.yml new file mode 100644 index 000000000..f5d12a739 --- /dev/null +++ b/new/detector/composition/ruby/testdata/scope_rule.yml @@ -0,0 +1,29 @@ +languages: + - ruby +patterns: + - pattern: scope_cursor($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: cursor + - pattern: scope_nested($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: nested + - pattern: scope_result($) + filters: + - variable: USER_INPUT + detection: scope_test_user_input + scope: result +auxiliary: + - id: scope_test_user_input + patterns: + - params[$<_>] +severity: high +metadata: + description: Test detection filter scopes + remediation_message: Test detection filter scopes + cwe_id: + - 42 + id: scope_test diff --git a/new/detector/evaluator/evaluator.go b/new/detector/evaluator/evaluator.go index 8047e6d8e..ccde521e5 100644 --- a/new/detector/evaluator/evaluator.go +++ b/new/detector/evaluator/evaluator.go @@ -8,6 +8,7 @@ import ( "github.com/bearer/bearer/new/language/implementation" langtree "github.com/bearer/bearer/new/language/tree" languagetypes "github.com/bearer/bearer/new/language/types" + "github.com/bearer/bearer/pkg/commands/process/settings" "golang.org/x/exp/slices" ) @@ -45,31 +46,41 @@ func (evaluator *evaluator) FileName() string { return evaluator.fileName } -func (evaluator *evaluator) ForTree( +func (evaluator *evaluator) Evaluate( rootNode *langtree.Node, detectorType, sanitizerDetectorType string, + scope settings.RuleReferenceScope, followFlow bool, ) ([]*types.Detection, error) { if rootNode == nil { return nil, nil } + nestedDetections, err := evaluator.detectorSet.NestedDetections(detectorType) + if err != nil { + return nil, err + } + var result []*types.Detection var nestedMode bool if err := rootNode.Walk(func(node *langtree.Node, visitChildren func() error) error { + if scope == settings.RESULT_SCOPE && !evaluator.langImplementation.ContributesToResult(node) { + return nil + } + if nestedMode && !evaluator.langImplementation.PassthroughNested(node) { return nil } - detections, sanitized, err := evaluator.sanitizedNodeDetections(node, detectorType, sanitizerDetectorType) + detections, sanitized, err := evaluator.sanitizedNodeDetections(node, detectorType, sanitizerDetectorType, scope) if sanitized || err != nil { return err } if followFlow { for _, unifiedNode := range node.UnifiedNodes() { - unifiedNodeDetections, err := evaluator.ForTree(unifiedNode, detectorType, sanitizerDetectorType, true) + unifiedNodeDetections, err := evaluator.Evaluate(unifiedNode, detectorType, sanitizerDetectorType, scope, true) if err != nil { return err } @@ -78,23 +89,19 @@ func (evaluator *evaluator) ForTree( } } - previousNestedMode := nestedMode + result = append(result, detections...) - if len(detections) != 0 { - nestedDetections, err := evaluator.detectorSet.NestedDetections(detectorType) - if err != nil { - return err - } + if scope != settings.CURSOR_SCOPE { + parentNestedMode := nestedMode - if !nestedDetections { + if len(detections) != 0 && nestedDetections { nestedMode = true } - } - result = append(result, detections...) + err = visitChildren() + nestedMode = parentNestedMode + } - err = visitChildren() - nestedMode = previousNestedMode return err }); err != nil { return nil, err @@ -103,34 +110,6 @@ func (evaluator *evaluator) ForTree( return result, nil } -func (evaluator *evaluator) ForNode( - node *langtree.Node, - detectorType, sanitizerDetectorType string, - followFlow bool, -) ([]*types.Detection, error) { - if node == nil { - return nil, nil - } - - detections, sanitized, err := evaluator.sanitizedNodeDetections(node, detectorType, sanitizerDetectorType) - if sanitized || err != nil { - return nil, err - } - - if followFlow { - for _, unifiedNode := range node.UnifiedNodes() { - unifiedNodeDetections, err := evaluator.ForNode(unifiedNode, detectorType, sanitizerDetectorType, true) - if err != nil { - return nil, err - } - - detections = append(detections, unifiedNodeDetections...) - } - } - - return detections, nil -} - func (evaluator *evaluator) ruleDisabledForNode(ruleId string, node *langtree.Node) bool { nodesToIgnore := evaluator.rulesDisabledForNodes[ruleId] if nodesToIgnore == nil { @@ -200,25 +179,27 @@ func mapNodesToDisabledRules(rootNode *langtree.Node) map[string][]*langtree.Nod func (evaluator *evaluator) sanitizedNodeDetections( node *langtree.Node, detectorType, sanitizerDetectorType string, + scope settings.RuleReferenceScope, ) ([]*types.Detection, bool, error) { if sanitizerDetectorType != "" { - sanitizerDetections, err := evaluator.nonUnifiedNodeDetections(node, sanitizerDetectorType) + sanitizerDetections, err := evaluator.nonUnifiedNodeDetections(node, sanitizerDetectorType, scope) if len(sanitizerDetections) != 0 || err != nil { return nil, true, err } } - detections, err := evaluator.nonUnifiedNodeDetections(node, detectorType) + detections, err := evaluator.nonUnifiedNodeDetections(node, detectorType, scope) return detections, false, err } func (evaluator *evaluator) nonUnifiedNodeDetections( node *langtree.Node, detectorType string, + scope settings.RuleReferenceScope, ) ([]*types.Detection, error) { nodeDetections, ok := evaluator.detectionCache[node.ID()] if !ok { - err := evaluator.detectAtNode(node, detectorType) + err := evaluator.detectAtNode(node, detectorType, scope) if err != nil { return nil, err } @@ -230,7 +211,7 @@ func (evaluator *evaluator) nonUnifiedNodeDetections( return detections, nil } - err := evaluator.detectAtNode(node, detectorType) + err := evaluator.detectAtNode(node, detectorType, scope) if err != nil { return nil, err } @@ -238,60 +219,17 @@ func (evaluator *evaluator) nonUnifiedNodeDetections( return nodeDetections[detectorType], nil } -func (evaluator *evaluator) TreeHas( - rootNode *langtree.Node, - detectorType, - sanitizerDetectorType string, -) (bool, error) { - var result bool - - if err := rootNode.Walk(func(node *langtree.Node, visitChildren func() error) error { - detections, sanitized, err := evaluator.sanitizedNodeDetections(node, detectorType, sanitizerDetectorType) - if sanitized || err != nil { - return err - } - - if len(detections) != 0 { - result = true - return nil - } - - for _, unifiedNode := range node.UnifiedNodes() { - hasDetection, err := evaluator.TreeHas(unifiedNode, detectorType, sanitizerDetectorType) - if err != nil { - return err - } - - if hasDetection { - result = true - return nil - } - } - - return visitChildren() - }); err != nil { - return false, err - } - - return result, nil -} - -func (evaluator *evaluator) NodeHas(node *langtree.Node, detectorType, sanitizerDetectorType string) (bool, error) { - detections, err := evaluator.ForNode(node, detectorType, sanitizerDetectorType, true) - if err != nil { - return false, err - } - - return len(detections) != 0, nil -} - -func (evaluator *evaluator) detectAtNode(node *langtree.Node, detectorType string) error { +func (evaluator *evaluator) detectAtNode( + node *langtree.Node, + detectorType string, + scope settings.RuleReferenceScope, +) error { if evaluator.ruleDisabledForNode(detectorType, node) { return nil } return evaluator.withCycleProtection(node, detectorType, func() error { - detections, err := evaluator.detectorSet.DetectAt(node, detectorType, evaluator) + detections, err := evaluator.detectorSet.DetectAt(node, detectorType, scope, evaluator) if err != nil { return err } diff --git a/new/detector/implementation/custom/custom.go b/new/detector/implementation/custom/custom.go index 4af011e9d..2574d9bdb 100644 --- a/new/detector/implementation/custom/custom.go +++ b/new/detector/implementation/custom/custom.go @@ -62,6 +62,7 @@ func (detector *customDetector) Name() string { func (detector *customDetector) DetectAt( node *tree.Node, + ruleReferenceType settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { var detectionsData []interface{} @@ -74,6 +75,7 @@ func (detector *customDetector) DetectAt( for _, result := range results { filtersMatch, datatypeDetections, variableNodes, err := matchAllFilters( + ruleReferenceType, result, evaluator, pattern.Filters, diff --git a/new/detector/implementation/custom/filter.go b/new/detector/implementation/custom/filter.go index 338bc33ec..df4704937 100644 --- a/new/detector/implementation/custom/filter.go +++ b/new/detector/implementation/custom/filter.go @@ -14,6 +14,7 @@ import ( ) func matchFilter( + scope settings.RuleReferenceScope, result *languagetypes.PatternQueryResult, evaluator types.Evaluator, variableNodes map[string]*tree.Node, @@ -21,7 +22,7 @@ func matchFilter( rules map[string]*settings.Rule, ) (*bool, []*types.Detection, error) { if filter.Not != nil { - match, _, err := matchFilter(result, evaluator, variableNodes, *filter.Not, rules) + match, _, err := matchFilter(scope, result, evaluator, variableNodes, *filter.Not, rules) if match == nil { return nil, nil, err } @@ -29,7 +30,7 @@ func matchFilter( } if len(filter.Either) != 0 { - return matchEitherFilters(result, evaluator, variableNodes, filter.Either, rules) + return matchEitherFilters(scope, result, evaluator, variableNodes, filter.Either, rules) } if filter.FilenameRegex != nil { @@ -43,12 +44,18 @@ func matchFilter( } if filter.Detection != "" { + effectiveScope := filter.Scope + if scope == settings.RESULT_SCOPE { + effectiveScope = settings.RESULT_SCOPE + } + return matchDetectionFilter( result, evaluator, variableNodes, node, filter.Detection, + effectiveScope, filter.Contains == nil || *filter.Contains, rules, ) @@ -59,6 +66,7 @@ func matchFilter( } func matchAllFilters( + scope settings.RuleReferenceScope, result *languagetypes.PatternQueryResult, evaluator types.Evaluator, filters []settings.PatternFilter, @@ -72,7 +80,7 @@ func matchAllFilters( } for _, filter := range filters { - matched, subDataTypeDetections, err := matchFilter(result, evaluator, variableNodes, filter, rules) + matched, subDataTypeDetections, err := matchFilter(scope, result, evaluator, variableNodes, filter, rules) if matched == nil || !*matched || err != nil { return false, nil, nil, err } @@ -84,6 +92,7 @@ func matchAllFilters( } func matchEitherFilters( + scope settings.RuleReferenceScope, result *languagetypes.PatternQueryResult, evaluator types.Evaluator, variableNodes map[string]*tree.Node, @@ -95,7 +104,7 @@ func matchEitherFilters( oneNotMatched := false for _, subFilter := range filters { - subMatch, subDatatypeDetections, err := matchFilter(result, evaluator, variableNodes, subFilter, rules) + subMatch, subDatatypeDetections, err := matchFilter(scope, result, evaluator, variableNodes, subFilter, rules) if err != nil { return nil, nil, err } @@ -122,28 +131,22 @@ func matchDetectionFilter( variableNodes map[string]*tree.Node, node *tree.Node, detectorType string, + scope settings.RuleReferenceScope, contains bool, rules map[string]*settings.Rule, ) (*bool, []*types.Detection, error) { - var evaluateDetections func(*tree.Node, string, string, bool) ([]*types.Detection, error) - if contains { - evaluateDetections = evaluator.ForTree - } else { - evaluateDetections = evaluator.ForNode - } - sanitizerRuleID := "" if rule, ok := rules[detectorType]; ok { sanitizerRuleID = rule.SanitizerRuleID } if detectorType == "datatype" { - detections, err := evaluateDetections(node, "datatype", sanitizerRuleID, true) + detections, err := evaluator.Evaluate(node, "datatype", sanitizerRuleID, scope, true) return boolPointer(len(detections) != 0), detections, err } - detections, err := evaluateDetections(node, detectorType, sanitizerRuleID, true) + detections, err := evaluator.Evaluate(node, detectorType, sanitizerRuleID, scope, true) var datatypeDetections []*types.Detection ignoredVariables := getIgnoredVariables(detections) diff --git a/new/detector/implementation/generic/datatype/datatype.go b/new/detector/implementation/generic/datatype/datatype.go index e010f1d34..3d191fe58 100644 --- a/new/detector/implementation/generic/datatype/datatype.go +++ b/new/detector/implementation/generic/datatype/datatype.go @@ -6,6 +6,7 @@ import ( "github.com/bearer/bearer/new/language/tree" languagetypes "github.com/bearer/bearer/new/language/types" classificationschema "github.com/bearer/bearer/pkg/classification/schema" + "github.com/bearer/bearer/pkg/commands/process/settings" "github.com/bearer/bearer/pkg/report/detectors" "github.com/bearer/bearer/pkg/report/schema" "github.com/bearer/bearer/pkg/util/classify" @@ -43,9 +44,10 @@ func (detector *datatypeDetector) NestedDetections() bool { func (detector *datatypeDetector) DetectAt( node *tree.Node, + _ settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { - objectDetections, err := evaluator.ForNode(node, "object", "", false) + objectDetections, err := evaluator.Evaluate(node, "object", "", settings.CURSOR_SCOPE, false) if err != nil { return nil, err } diff --git a/new/detector/implementation/generic/generic.go b/new/detector/implementation/generic/generic.go index 07b6a5f57..c60e5e4ec 100644 --- a/new/detector/implementation/generic/generic.go +++ b/new/detector/implementation/generic/generic.go @@ -6,10 +6,14 @@ import ( generictypes "github.com/bearer/bearer/new/detector/implementation/generic/types" "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" ) -func GetNonVirtualObjects(evaluator types.Evaluator, node *tree.Node) ([]*types.Detection, error) { - detections, err := evaluator.ForNode(node, "object", "", true) +func GetNonVirtualObjects( + evaluator types.Evaluator, + node *tree.Node, +) ([]*types.Detection, error) { + detections, err := evaluator.Evaluate(node, "object", "", settings.CURSOR_SCOPE, true) if err != nil { return nil, err } @@ -84,7 +88,7 @@ func ProjectObject( } func GetStringValue(node *tree.Node, evaluator types.Evaluator) (string, bool, error) { - detections, err := evaluator.ForNode(node, "string", "", true) + detections, err := evaluator.Evaluate(node, "string", "", settings.CURSOR_SCOPE, true) if err != nil { return "", false, err } diff --git a/new/detector/implementation/generic/insecureurl/insecureurl.go b/new/detector/implementation/generic/insecureurl/insecureurl.go index 35a99c7df..f663092e0 100644 --- a/new/detector/implementation/generic/insecureurl/insecureurl.go +++ b/new/detector/implementation/generic/insecureurl/insecureurl.go @@ -5,6 +5,7 @@ import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" generictypes "github.com/bearer/bearer/new/detector/implementation/generic/types" languagetypes "github.com/bearer/bearer/new/language/types" @@ -27,9 +28,10 @@ func (detector *insecureURLDetector) Name() string { func (detector *insecureURLDetector) DetectAt( node *tree.Node, + _ settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { - detections, err := evaluator.ForNode(node, "string", "", false) + detections, err := evaluator.Evaluate(node, "string", "", settings.CURSOR_SCOPE, false) if err != nil { return nil, err } diff --git a/new/detector/implementation/generic/stringliteral/stringliteral.go b/new/detector/implementation/generic/stringliteral/stringliteral.go index decae8e93..63a6e19a7 100644 --- a/new/detector/implementation/generic/stringliteral/stringliteral.go +++ b/new/detector/implementation/generic/stringliteral/stringliteral.go @@ -3,6 +3,7 @@ package stringliteral import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" generictypes "github.com/bearer/bearer/new/detector/implementation/generic/types" languagetypes "github.com/bearer/bearer/new/language/types" @@ -22,9 +23,10 @@ func (detector *stringLiteralDetector) Name() string { func (detector *stringLiteralDetector) DetectAt( node *tree.Node, + _ settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { - detections, err := evaluator.ForNode(node, "string", "", false) + detections, err := evaluator.Evaluate(node, "string", "", settings.CURSOR_SCOPE, false) if err != nil { return nil, err } diff --git a/new/detector/implementation/java/object/object.go b/new/detector/implementation/java/object/object.go index 1d15d40ed..5f8529ea6 100644 --- a/new/detector/implementation/java/object/object.go +++ b/new/detector/implementation/java/object/object.go @@ -5,6 +5,7 @@ import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" "github.com/rs/zerolog/log" "github.com/bearer/bearer/new/detector/implementation/generic" @@ -69,6 +70,7 @@ func (detector *objectDetector) NestedDetections() bool { func (detector *objectDetector) DetectAt( node *tree.Node, + ruleReferenceType settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { log.Debug().Msgf("node is %s", node.Debug()) diff --git a/new/detector/implementation/java/string/string.go b/new/detector/implementation/java/string/string.go index 305f1ea57..603e46506 100644 --- a/new/detector/implementation/java/string/string.go +++ b/new/detector/implementation/java/string/string.go @@ -3,6 +3,7 @@ package string import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" generictypes "github.com/bearer/bearer/new/detector/implementation/generic/types" languagetypes "github.com/bearer/bearer/new/language/types" @@ -22,6 +23,7 @@ func (detector *stringDetector) Name() string { func (detector *stringDetector) DetectAt( node *tree.Node, + ruleReferenceType settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { if node.Type() == "string_literal" { diff --git a/new/detector/implementation/javascript/object/object.go b/new/detector/implementation/javascript/object/object.go index ea7821968..40e45966a 100644 --- a/new/detector/implementation/javascript/object/object.go +++ b/new/detector/implementation/javascript/object/object.go @@ -5,6 +5,7 @@ import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" "github.com/bearer/bearer/pkg/util/stringutil" "github.com/bearer/bearer/new/detector/implementation/generic" @@ -114,6 +115,7 @@ func (detector *objectDetector) NestedDetections() bool { func (detector *objectDetector) DetectAt( node *tree.Node, + _ settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { detections, err := detector.getObject(node, evaluator) @@ -145,7 +147,7 @@ func (detector *objectDetector) getObject( } for _, spreadResult := range spreadResults { - detections, err := evaluator.ForNode(spreadResult["identifier"], "object", "", true) + detections, err := evaluator.Evaluate(spreadResult["identifier"], "object", "", settings.CURSOR_SCOPE, true) if err != nil { return nil, err @@ -175,7 +177,7 @@ func (detector *objectDetector) getObject( continue } - propertyObjects, err := evaluator.ForTree(result["value"], "object", "", true) + propertyObjects, err := evaluator.Evaluate(result["value"], "object", "", settings.NESTED_SCOPE, true) if err != nil { return nil, err } diff --git a/new/detector/implementation/javascript/object/projection.go b/new/detector/implementation/javascript/object/projection.go index 2828e7a4c..1660654cf 100644 --- a/new/detector/implementation/javascript/object/projection.go +++ b/new/detector/implementation/javascript/object/projection.go @@ -5,6 +5,7 @@ import ( generictypes "github.com/bearer/bearer/new/detector/implementation/generic/types" "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" "github.com/bearer/bearer/pkg/util/stringutil" ) @@ -97,7 +98,7 @@ func (detector *objectDetector) getCallProjections( var properties []generictypes.Property - functionDetections, err := evaluator.ForTree(result["function"], "object", "", true) + functionDetections, err := evaluator.Evaluate(result["function"], "object", "", settings.NESTED_SCOPE, true) if len(functionDetections) == 0 || err != nil { return nil, err } diff --git a/new/detector/implementation/javascript/string/string.go b/new/detector/implementation/javascript/string/string.go index c7346b9ba..09a292d0f 100644 --- a/new/detector/implementation/javascript/string/string.go +++ b/new/detector/implementation/javascript/string/string.go @@ -3,6 +3,7 @@ package string import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" "github.com/bearer/bearer/pkg/util/stringutil" "github.com/bearer/bearer/new/detector/implementation/generic" @@ -24,6 +25,7 @@ func (detector *stringDetector) Name() string { func (detector *stringDetector) DetectAt( node *tree.Node, + ruleReferenceType settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { switch node.Type() { diff --git a/new/detector/implementation/ruby/object/object.go b/new/detector/implementation/ruby/object/object.go index 02cc418bb..92e4e77f2 100644 --- a/new/detector/implementation/ruby/object/object.go +++ b/new/detector/implementation/ruby/object/object.go @@ -5,6 +5,7 @@ import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" "github.com/bearer/bearer/new/detector/implementation/generic" generictypes "github.com/bearer/bearer/new/detector/implementation/generic/types" @@ -93,6 +94,7 @@ func (detector *objectDetector) NestedDetections() bool { func (detector *objectDetector) DetectAt( node *tree.Node, + _ settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { detections, err := detector.getHash(node, evaluator) @@ -136,7 +138,7 @@ func (detector *objectDetector) getHash( continue } - propertyObjects, err := evaluator.ForTree(result["value"], "object", "", true) + propertyObjects, err := evaluator.Evaluate(result["value"], "object", "", settings.NESTED_SCOPE, true) if err != nil { return nil, err } @@ -176,7 +178,7 @@ func (detector *objectDetector) getKeywordArgument( return nil, nil } - propertyObjects, err := evaluator.ForTree(result["value"], "object", "", true) + propertyObjects, err := evaluator.Evaluate(result["value"], "object", "", settings.NESTED_SCOPE, true) if err != nil { return nil, err } diff --git a/new/detector/implementation/ruby/string/string.go b/new/detector/implementation/ruby/string/string.go index 351ce6780..5dc1f6d97 100644 --- a/new/detector/implementation/ruby/string/string.go +++ b/new/detector/implementation/ruby/string/string.go @@ -5,6 +5,7 @@ import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" generictypes "github.com/bearer/bearer/new/detector/implementation/generic/types" languagetypes "github.com/bearer/bearer/new/language/types" @@ -24,6 +25,7 @@ func (detector *stringDetector) Name() string { func (detector *stringDetector) DetectAt( node *tree.Node, + _ settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]interface{}, error) { switch node.Type() { @@ -53,7 +55,7 @@ func concatenateChildren(node *tree.Node, evaluator types.Evaluator) ([]interfac continue } - detections, err := evaluator.ForNode(child, "string", "", true) + detections, err := evaluator.Evaluate(child, "string", "", settings.CURSOR_SCOPE, true) if err != nil { return nil, err } diff --git a/new/detector/set/set.go b/new/detector/set/set.go index bc82d5d89..104451e7e 100644 --- a/new/detector/set/set.go +++ b/new/detector/set/set.go @@ -5,6 +5,7 @@ import ( "github.com/bearer/bearer/new/detector/types" "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" ) type set struct { @@ -41,6 +42,7 @@ func (set *set) NestedDetections(detectorType string) (bool, error) { func (set *set) DetectAt( node *tree.Node, detectorType string, + ruleReferenceType settings.RuleReferenceScope, evaluator types.Evaluator, ) ([]*types.Detection, error) { detector, err := set.lookupDetector(detectorType) @@ -48,7 +50,7 @@ func (set *set) DetectAt( return nil, err } - detectionsData, err := detector.DetectAt(node, evaluator) + detectionsData, err := detector.DetectAt(node, ruleReferenceType, evaluator) if err != nil { return nil, err } diff --git a/new/detector/types/types.go b/new/detector/types/types.go index 8e84d1a10..e66d81cbf 100644 --- a/new/detector/types/types.go +++ b/new/detector/types/types.go @@ -2,6 +2,7 @@ package types import ( "github.com/bearer/bearer/new/language/tree" + "github.com/bearer/bearer/pkg/commands/process/settings" "github.com/bearer/bearer/pkg/util/file" ) @@ -12,10 +13,13 @@ type Detection struct { } type Evaluator interface { - ForTree(rootNode *tree.Node, detectorType, sanitizerDetectorType string, followFlow bool) ([]*Detection, error) - ForNode(node *tree.Node, detectorType, sanitizerDetectorType string, followFlow bool) ([]*Detection, error) - TreeHas(rootNode *tree.Node, detectorType, sanitizerDetectorType string) (bool, error) - NodeHas(node *tree.Node, detectorType, sanitizerDetectorType string) (bool, error) + Evaluate( + rootNode *tree.Node, + detectorType, + sanitizerDetectorType string, + scope settings.RuleReferenceScope, + followFlow bool, + ) ([]*Detection, error) FileName() string } @@ -24,13 +28,18 @@ type DetectorSet interface { DetectAt( node *tree.Node, detectorType string, + ruleReferenceType settings.RuleReferenceScope, evaluator Evaluator, ) ([]*Detection, error) } type Detector interface { Name() string - DetectAt(node *tree.Node, evaluator Evaluator) ([]interface{}, error) + DetectAt( + node *tree.Node, + ruleReferenceType settings.RuleReferenceScope, + evaluator Evaluator, + ) ([]interface{}, error) NestedDetections() bool Close() } diff --git a/new/language/implementation/implementation.go b/new/language/implementation/implementation.go index 4160e7e1d..2619b8cd0 100644 --- a/new/language/implementation/implementation.go +++ b/new/language/implementation/implementation.go @@ -121,6 +121,8 @@ type Implementation interface { ShouldSkipNode(node *tree.Node) bool PassthroughNested(node *tree.Node) bool + + ContributesToResult(node *tree.Node) bool } type Scope struct { diff --git a/new/language/implementation/java/java.go b/new/language/implementation/java/java.go index 34c2d4031..8e3cd999b 100644 --- a/new/language/implementation/java/java.go +++ b/new/language/implementation/java/java.go @@ -305,3 +305,32 @@ func (*javaImplementation) PassthroughNested(node *tree.Node) bool { return slices.Contains(passthroughMethods, method) || slices.Contains(passthroughMethods, wildcardMethod) } + +func (*javaImplementation) ContributesToResult(node *tree.Node) bool { + // Statements don't have results + if strings.HasSuffix(node.Type(), "_statement") { + return false + } + + // Switch case + if node.Type() == "switch_label" { + return false + } + + parent := node.Parent() + if parent == nil { + return true + } + + // Must not be a ternary/switch condition + if node.Equal(parent.ChildByFieldName("condition")) { + return false + } + + // Must be the arguments of calls + if parent.Type() == "method_invocation" && !node.Equal(parent.ChildByFieldName("arguments")) { + return false + } + + return true +} diff --git a/new/language/implementation/javascript/javascript.go b/new/language/implementation/javascript/javascript.go index 56609c58a..3713f1c6a 100644 --- a/new/language/implementation/javascript/javascript.go +++ b/new/language/implementation/javascript/javascript.go @@ -275,3 +275,27 @@ func (*javascriptImplementation) PassthroughNested(node *tree.Node) bool { return slices.Contains(passthroughMethods, method) || slices.Contains(passthroughMethods, wildcardMethod) } + +func (*javascriptImplementation) ContributesToResult(node *tree.Node) bool { + // Statements don't have results + if strings.HasSuffix(node.Type(), "_statement") { + return false + } + + parent := node.Parent() + if parent == nil { + return true + } + + // Must not be a ternary condition + if parent.Type() == "ternary_expression" && node.Equal(parent.ChildByFieldName("condition")) { + return false + } + + // Must be the arguments of calls + if parent.Type() == "call_expression" && !node.Equal(parent.ChildByFieldName("arguments")) { + return false + } + + return true +} diff --git a/new/language/implementation/ruby/ruby.go b/new/language/implementation/ruby/ruby.go index fdd989bbb..fe0651123 100644 --- a/new/language/implementation/ruby/ruby.go +++ b/new/language/implementation/ruby/ruby.go @@ -296,3 +296,39 @@ func (*rubyImplementation) PassthroughNested(node *tree.Node) bool { return slices.Contains(passthroughMethods, receiverMethod) || slices.Contains(passthroughMethods, wildcardMethod) } + +func (*rubyImplementation) ContributesToResult(node *tree.Node) bool { + parent := node.Parent() + if parent == nil { + return true + } + + // Must not be a condition + if node.Equal(parent.ChildByFieldName("condition")) { + return false + } + + // Must not be a case value + if parent.Type() == "case" && node.Equal(parent.ChildByFieldName("value")) { + return false + } + + // Must not be a case-when pattern + if parent.Type() == "when" && node.Equal(parent.ChildByFieldName("pattern")) { + return false + } + + // Must be the last expression in an expression block + if slices.Contains([]string{"then", "else"}, parent.Type()) { + if !node.Equal(parent.Child(parent.ChildCount() - 1)) { + return false + } + } + + // Must be the arguments of calls + if parent.Type() == "call" && !node.Equal(parent.ChildByFieldName("arguments")) { + return false + } + + return true +} diff --git a/pkg/commands/process/settings/settings.go b/pkg/commands/process/settings/settings.go index 63cdca88d..8c3b6d2cc 100644 --- a/pkg/commands/process/settings/settings.go +++ b/pkg/commands/process/settings/settings.go @@ -74,6 +74,14 @@ const ( STORED_DATA_TYPES MatchOn = "stored_data_types" ) +type RuleReferenceScope string + +const ( + CURSOR_SCOPE RuleReferenceScope = "cursor" + NESTED_SCOPE RuleReferenceScope = "nested" + RESULT_SCOPE RuleReferenceScope = "result" +) + type LoadRulesResult struct { BuiltInRules map[string]*Rule Rules map[string]*Rule @@ -176,20 +184,22 @@ type Rule struct { } type PatternFilter struct { - Not *PatternFilter `mapstructure:"not" json:"not" yaml:"not"` - Either []PatternFilter `mapstructure:"either" json:"either" yaml:"either"` - Variable string `mapstructure:"variable" json:"variable" yaml:"variable"` - Detection string `mapstructure:"detection" json:"detection" yaml:"detection"` - Contains *bool `mapstructure:"contains" json:"contains" yaml:"contains"` - Regex *Regexp `mapstructure:"regex" json:"regex" yaml:"regex"` - Values []string `mapstructure:"values" json:"values" yaml:"values"` - LengthLessThan *int `mapstructure:"length_less_than" json:"length_less_than" yaml:"length_less_than"` - LessThan *int `mapstructure:"less_than" json:"less_than" yaml:"less_than"` - LessThanOrEqual *int `mapstructure:"less_than_or_equal" json:"less_than_or_equal" yaml:"less_than_or_equal"` - GreaterThan *int `mapstructure:"greater_than" json:"greater_than" yaml:"greater_than"` - GreaterThanOrEqual *int `mapstructure:"greater_than_or_equal" json:"greater_than_or_equal" yaml:"greater_than_or_equal"` - StringRegex *Regexp `mapstructure:"string_regex" json:"string_regex" yaml:"string_regex"` - FilenameRegex *Regexp `mapstructure:"filename_regex" json:"filename_regex" yaml:"filename_regex"` + Not *PatternFilter `mapstructure:"not" json:"not" yaml:"not"` + Either []PatternFilter `mapstructure:"either" json:"either" yaml:"either"` + Variable string `mapstructure:"variable" json:"variable" yaml:"variable"` + Detection string `mapstructure:"detection" json:"detection" yaml:"detection"` + Scope RuleReferenceScope `mapstructure:"scope" json:"scope" yaml:"scope"` + // Contains is deprecated in favour of Scope + Contains *bool `mapstructure:"contains" json:"contains" yaml:"contains"` + Regex *Regexp `mapstructure:"regex" json:"regex" yaml:"regex"` + Values []string `mapstructure:"values" json:"values" yaml:"values"` + LengthLessThan *int `mapstructure:"length_less_than" json:"length_less_than" yaml:"length_less_than"` + LessThan *int `mapstructure:"less_than" json:"less_than" yaml:"less_than"` + LessThanOrEqual *int `mapstructure:"less_than_or_equal" json:"less_than_or_equal" yaml:"less_than_or_equal"` + GreaterThan *int `mapstructure:"greater_than" json:"greater_than" yaml:"greater_than"` + GreaterThanOrEqual *int `mapstructure:"greater_than_or_equal" json:"greater_than_or_equal" yaml:"greater_than_or_equal"` + StringRegex *Regexp `mapstructure:"string_regex" json:"string_regex" yaml:"string_regex"` + FilenameRegex *Regexp `mapstructure:"filename_regex" json:"filename_regex" yaml:"filename_regex"` } type RulePattern struct { @@ -311,6 +321,31 @@ func (rulePattern *RulePattern) UnmarshalYAML(unmarshal func(interface{}) error) return unmarshal((*rawRulePattern)(rulePattern)) } +func (filter *PatternFilter) UnmarshalYAML(unmarshal func(interface{}) error) error { + type wrapper PatternFilter + var wrapped wrapper + if err := unmarshal(&wrapped); err != nil { + return err + } + + *filter = PatternFilter(wrapped) + + // Default Scope to "contains" and maintain backwards compatibility with rules + // using the `contains` flag + if filter.Detection != "" { + if filter.Contains != nil { + if !*filter.Contains { + filter.Scope = CURSOR_SCOPE + } + } + if filter.Scope == "" { + filter.Scope = NESTED_SCOPE + } + } + + return nil +} + func DefaultPolicies() map[string]*Policy { policies := make(map[string]*Policy) var policy []*Policy