Skip to main content

rego_recursion_error: rule {name} is recursive

An important property of Rego compared to general purpose programming languages is that policy evaluation should be known to terminate. Some common programming constructs, like while loops, or recursive references, can't reasonably provide such guarantees, and are therefore not allowed in Rego. Recursion errors in Rego happens when a rule (or function, which is a special type of rule) makes a reference to either itself, or another rule that references the rule that made the original reference.

StageCategoryMessage
compilationrego_recursion_errorrule {name} is recursive: ref -> ref [-> ...]
compilationrego_recursion_errorrule {name} may be recursive: ref -> ref [-> ...] ({ref} refers to …)

Examples​

Recursive reference to the same rule:

package policy

rule_a := rule_a
1 error occurred: policy.rego:3: rego_recursion_error: rule data.policy.rule_a is recursive: data.policy.rule_a -> data.policy.rule_a

Recursive references between two rules:

package policy

rule_a := rule_b

rule_b := rule_a
2 errors occurred:
policy.rego:5: rego_recursion_error: rule data.policy.rule_b is recursive: data.policy.rule_b -> data.policy.rule_a -> data.policy.rule_b
policy.rego:3: rego_recursion_error: rule data.policy.rule_a is recursive: data.policy.rule_a -> data.policy.rule_b -> data.policy.rule_a

While the examples above demonstrate the principle of recursive references, the error is rarely this obvious. The most common occurrence of recursion in real-world policy happens when the global data document is referenced without a specific path, or when parts of the path are dynamic, and potentially recursive.

package policy

# recursive since `data` includes `data.policy` which is the current package
rule := data
package policy

# the compiler can't know what `input.path` might provide, so this is _potentially_
# recursive (as `input.path` could be "policy") and as such flagged by the compiler
rule := data[input.path]
1 error occurred: policy.rego:5: rego_recursion_error: rule data.policy.rule may be recursive: data.policy.rule -> data.policy.rule (data[input.path] refers to data.policy.rule if input.path is "policy")

The compiler reports may be recursive when the cycle passes through a dynamic part of a reference, like input.path above, or a variable that is bound elsewhere in the rule. It can't rule the recursion out, so the policy is still rejected. The part in parentheses names the reference that closes the cycle and, when it can, the values that would make it do so, and where those values come from:

package policy

deny contains message if {
some name in ["policy", "other"]
some message in data[name].deny
}
1 error occurred: policy.rego:3: rego_recursion_error: rule data.policy.deny may be recursive: data.policy.deny -> data.policy.deny (data[name].deny refers to data.policy.deny if name is "policy" (name is one of ["policy", "other"]))

Here "policy" is in the list, so the rule is recursive whenever it's evaluated. If a later condition excluded "policy", it wouldn't be, but the compiler doesn't analyze those conditions and rejects the policy either way.

How To Fix It​

Ensure that dynamic references to data aren't potentially recursive by using a leading static path that doesn't match the current package name.

package policy

import future.keywords.contains
import future.keywords.if
import future.keywords.in

deny contains message if {
# `data.rules` can't be recursive since the first path component ("rules") is static
# and doesn't match the name of the current package ("policy")
some message in data.rules[_].deny
}

# not recursive regardless of `input` as "children" never references "policy"
rule := data.children[input.path]