Skip to content

Commit 61ab730

Browse files
authored
Rule: invalid-regexp (#1937)
Simple, but a nice addition! Fixes #1933 Signed-off-by: Anders Eknert <anders.eknert@apple.com>
1 parent 6a364fb commit 61ab730

13 files changed

Lines changed: 209 additions & 42 deletions

File tree

bundle/regal/ast/regexp.rego

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package regal.ast
2+
3+
import data.regal.util
4+
5+
# METADATA
6+
# description: All regular expression 'literals' (represented as strings) found in the AST
7+
regexp.found.literals contains term if {
8+
# skip traversing refs if no builtin regex function calls are registered
9+
util.intersects(regexp.pattern_function_names, builtin_functions_called)
10+
11+
value := found.calls[_][_]
12+
13+
value[0].value[0].value == "regex"
14+
15+
# The name following "regex.", e.g. "match"
16+
name := value[0].value[1].value
17+
18+
some pos in regexp.pattern_functions[name]
19+
20+
term := value[pos]
21+
22+
term.type == "string"
23+
}
24+
25+
# METADATA
26+
# description: Mapping of regex.* functions and the position(s) of their "pattern" attributes
27+
regexp.pattern_functions := {
28+
"find_all_string_submatch_n": [1],
29+
"find_n": [1],
30+
"globs_match": [1, 2],
31+
"is_valid": [1],
32+
"match": [1],
33+
"replace": [2],
34+
"split": [1],
35+
"template_match": [1],
36+
}
37+
38+
# METADATA
39+
# description: Set of all regex.* function names that take a regex pattern as an argument
40+
regexp.pattern_function_names := {
41+
"regex.find_all_string_submatch_n",
42+
"regex.find_n",
43+
"regex.globs_match",
44+
"regex.is_valid",
45+
"regex.match",
46+
"regex.replace",
47+
"regex.split",
48+
"regex.template_match",
49+
}

bundle/regal/config/provided/data.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ rules:
2828
level: error
2929
invalid-metadata-attribute:
3030
level: error
31+
invalid-regexp:
32+
level: error
3133
leaked-internal-reference:
3234
level: error
3335
not-equals-in-loop:
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# METADATA
2+
# description: Invalid regular expression
3+
# related_resources:
4+
# - description: documentation
5+
# ref: https://www.openpolicyagent.org/projects/regal/rules/bugs/invalid-regexp
6+
package regal.rules.bugs["invalid-regexp"]
7+
8+
import data.regal.ast
9+
import data.regal.result
10+
11+
report contains violation if {
12+
some term in ast.regexp.found.literals
13+
14+
not regex.is_valid(term.value)
15+
16+
violation := result.fail(rego.metadata.chain(), result.location(term))
17+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package regal.rules.bugs["invalid-regexp_test"]
2+
3+
import data.regal.ast
4+
import data.regal.capabilities
5+
import data.regal.config
6+
import data.regal.util
7+
8+
import data.regal.rules.bugs["invalid-regexp"] as rule
9+
10+
test_fail_invalid_regexp if {
11+
r := rule.report with input as ast.policy("r := regex.match(`(`, input.string)")
12+
with config.capabilities as capabilities.provided
13+
14+
r == {{
15+
"category": "bugs",
16+
"description": "Invalid regular expression",
17+
"level": "error",
18+
"location": {
19+
"col": 18,
20+
"end": {
21+
"col": 21,
22+
"row": 3,
23+
},
24+
"file": "policy.rego",
25+
"row": 3,
26+
"text": "r := regex.match(`(`, input.string)",
27+
},
28+
"related_resources": [{
29+
"description": "documentation",
30+
"ref": "https://www.openpolicyagent.org/projects/regal/rules/bugs/invalid-regexp",
31+
}],
32+
"title": "invalid-regexp",
33+
}}
34+
}
35+
36+
test_fail_invalid_regexp_for_pattern_function[call] if {
37+
some call in [
38+
"regex.find_all_string_submatch_n(`(`, input.x, 1)",
39+
"regex.find_n(`(`, input.x, 1)",
40+
"regex.globs_match(`(`, `[abc]`)",
41+
"regex.globs_match(`[abc]`, `(`)",
42+
"regex.is_valid(`(`)",
43+
"regex.match(`(`, input.x)",
44+
"regex.replace(input.x, `(`, `y`)",
45+
"regex.split(`(`, input.x)",
46+
"regex.template_match(`(`, input.x, `<`, `>`)",
47+
]
48+
49+
r := rule.report with input as ast.policy($"r := {call}")
50+
with config.capabilities as capabilities.provided
51+
52+
util.single_set_item(r).location.text == $"r := {call}"
53+
}
54+
55+
test_pass_valid_regexp if {
56+
r := rule.report with input as ast.policy("r := regex.match(`[abc]`, input.string)")
57+
with config.capabilities as capabilities.provided
58+
59+
r == set()
60+
}

bundle/regal/rules/idiomatic/non-raw-regex-pattern/non_raw_regex_pattern.rego

Lines changed: 3 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -10,48 +10,12 @@ import data.regal.result
1010
import data.regal.util
1111

1212
report contains violation if {
13-
# skip traversing refs if no builtin regex function calls are registered
14-
util.intersects(_re_pattern_function_names, ast.builtin_functions_called)
13+
some term in ast.regexp.found.literals
1514

16-
value := ast.found.calls[_][_]
17-
18-
value[0].value[0].value == "regex"
19-
20-
# The name following "regex.", e.g. "match"
21-
name := value[0].value[1].value
22-
23-
some pos in _re_pattern_functions[name]
24-
25-
value[pos].type == "string"
26-
27-
loc := util.to_location_object(value[pos].location)
15+
loc := util.to_location_object(term.location)
2816
row := input.regal.file.lines[loc.row - 1]
2917

3018
substring(row, loc.col - 1, 1) == `"`
3119

32-
violation := result.fail(rego.metadata.chain(), result.location(value[pos]))
33-
}
34-
35-
# Mapping of regex.* functions and the position(s)
36-
# of their "pattern" attributes
37-
_re_pattern_functions := {
38-
"find_all_string_submatch_n": [1],
39-
"find_n": [1],
40-
"globs_match": [1, 2],
41-
"is_valid": [1],
42-
"match": [1],
43-
"replace": [2],
44-
"split": [1],
45-
"template_match": [1],
46-
}
47-
48-
_re_pattern_function_names := {
49-
"regex.find_all_string_submatch_n",
50-
"regex.find_n",
51-
"regex.globs_match",
52-
"regex.is_valid",
53-
"regex.match",
54-
"regex.replace",
55-
"regex.split",
56-
"regex.template_match",
20+
violation := result.fail(rego.metadata.chain(), result.location(term))
5721
}

docs/rules/bugs/import-shadows-rule.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@
55
**Category**: Bugs
66

77
**Avoid**
8+
89
```rego
910
package policy
1011
1112
import data.resources
1213
13-
# 'resources' shadowed by import
14+
# 'resources' shadowed by import
1415
resources contains resource if {
1516
# ...
1617
}
1718
```
1819

1920
**Prefer**
21+
2022
```rego
2123
package policy
2224
@@ -56,3 +58,7 @@ rules:
5658
# one of "error", "warning", "ignore"
5759
level: error
5860
```
61+
62+
## Related Resources
63+
64+
- GitHub: [Source Code](https://github.com/open-policy-agent/regal/blob/main/bundle/regal/rules/bugs/import-shadows-rule/import_shadows_rule.rego)

docs/rules/bugs/invalid-regexp.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# invalid-regexp
2+
3+
**Summary**: Invalid regular expression
4+
5+
**Category**: Bugs
6+
7+
**Avoid**
8+
9+
```rego
10+
package policy
11+
12+
invalid if regex.match(`[abc`, input.text)
13+
```
14+
15+
**Prefer**
16+
17+
```rego
18+
package policy
19+
20+
valid if regex.match(`[abc]`, input.text)
21+
```
22+
23+
## Rationale
24+
25+
An invalid regular expression typically fails silently (i.e. the result is undefined) at runtime when OPA evaluates the
26+
function call, or with a runtime error if the `show-builtin-errors` option is enabled. While hopefully caught by unit
27+
tests, tracking down a typo in a regular expression is still time consuming. This rule instead analyzes any regular
28+
expressions found in a policy as you author it (using OPA's own `regex.is_valid` function) and reports invalid patterns
29+
directly.
30+
31+
## Configuration Options
32+
33+
This linter rule provides the following configuration options:
34+
35+
```yaml
36+
rules:
37+
bugs:
38+
invalid-regexp:
39+
# one of "error", "warning", "ignore"
40+
level: error
41+
```
42+
43+
## Related Resources
44+
45+
- OPA Docs: [Regex Functions](https://www.openpolicyagent.org/docs/latest/policy-reference/#regex-functions)
46+
- GitHub: [Source Code](https://github.com/open-policy-agent/regal/blob/main/bundle/regal/rules/bugs/invalid-regexp/invalid_regexp.rego)

docs/rules/bugs/redundant-loop-count.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
**Category**: Bugs
66

77
**Avoid**
8+
89
```rego
910
package policy
1011
@@ -17,6 +18,7 @@ allow if {
1718
```
1819

1920
**Prefer**
21+
2022
```rego
2123
package policy
2224
@@ -80,3 +82,7 @@ rules:
8082
# one of "error", "warning", "ignore"
8183
level: error
8284
```
85+
86+
## Related Resources
87+
88+
- GitHub: [Source Code](https://github.com/open-policy-agent/regal/blob/main/bundle/regal/rules/bugs/redundant-loop-count/redundant_loop_count.rego)

docs/rules/idiomatic/superfluous-object-get.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,7 @@ rules:
4141
# one of "error", "warning", "ignore"
4242
level: error
4343
```
44+
45+
## Related Resources
46+
47+
- GitHub: [Source Code](https://github.com/open-policy-agent/regal/blob/main/bundle/regal/rules/idiomatic/superfluous-object-get/superfluous_object_get.rego)

docs/rules/imports/confusing-alias.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
**Category**: Imports
66

77
**Avoid**
8+
89
```rego
910
package policy
1011
@@ -14,6 +15,7 @@ import data.resources.users as employees
1415
```
1516

1617
**Prefer**
18+
1719
```rego
1820
package policy
1921
@@ -50,3 +52,7 @@ rules:
5052
# one of "error", "warning", "ignore"
5153
level: error
5254
```
55+
56+
## Related Resources
57+
58+
- GitHub: [Source Code](https://github.com/open-policy-agent/regal/blob/main/bundle/regal/rules/imports/confusing-alias/confusing_alias.rego)

0 commit comments

Comments
 (0)