CVE-2022-36085: Insufficient Information (NVD-CWE-noinfo)
value: Open Policy Agent (OPA) is an open source, general-purpose policy engine. The Rego compiler provides a (deprecated) `WithUnsafeBuiltins` function, which allows users to provide a set of built-in functions that should be deemed unsafe — and as such rejected — by the compiler if encountered in the policy compilation stage. A bypass of this protection has been found, where the use of the `with` keyword to mock such a built-in function (a feature introduced in OPA v0.40.0), isn’t taken into account by `WithUnsafeBuiltins`. Multiple conditions need to be met in order to create an adverse effect. Version 0.43.1 contains a patch for this issue. As a workaround, avoid using the `WithUnsafeBuiltins` function and use the `capabilities` feature instead.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2022-09-08T14:15Z
Repository: https://github.com/open-policy-agent/opa
Language: Go
Files changed: ast/compile.go, ast/compile_test.go, rego/rego_test.go
Commit date: 2022-09-07 20:00:18 +0200
Commit message: ast/compile: respect unsafeBuiltinMap for 'with' replacements (#5107)

The changes are necessary for both the Compiler and the QueryCompiler. Tests
have been added to ensure that the code path through the rego package has also
been fixed.

Fixes CVE-2022-36085.

Signed-off-by: Stephan Renatus <stephan.renatus@gmail.com>
Diff: diff --git a/ast/compile.go b/ast/compile.go
index 42db09882..f449fddcd 100644
--- a/ast/compile.go
+++ b/ast/compile.go
@@ -2194,11 +2194,11 @@ func (c *Compiler) rewriteWithModifiers() {
 		t := NewGenericTransformer(func(x interface{}) (interface{}, error) {
 			body, ok := x.(Body)
 			if !ok {
 				return x, nil
 			}
-			body, err := rewriteWithModifiersInBody(c, f, body)
+			body, err := rewriteWithModifiersInBody(c, c.unsafeBuiltinsMap, f, body)
 			if err != nil {
 				c.err(err)
 			}
 
 			return body, nil
@@ -2473,34 +2473,35 @@ func (qc *queryCompiler) checkTypes(_ *QueryContext, body Body) (Body, error) {
 
 	return body, nil
 }
 
 func (qc *queryCompiler) checkUnsafeBuiltins(_ *QueryContext, body Body) (Body, error) {
-	var unsafe map[string]struct{}
-	if qc.unsafeBuiltins != nil {
-		unsafe = qc.unsafeBuiltins
-	} else {
-		unsafe = qc.compiler.unsafeBuiltinsMap
-	}
-	errs := checkUnsafeBuiltins(unsafe, body)
+	errs := checkUnsafeBuiltins(qc.unsafeBuiltinsMap(), body)
 	if len(errs) > 0 {
 		return nil, errs
 	}
 	return body, nil
 }
 
+func (qc *queryCompiler) unsafeBuiltinsMap() map[string]struct{} {
+	if qc.unsafeBuiltins != nil {
+		return qc.unsafeBuiltins
+	}
+	return qc.compiler.unsafeBuiltinsMap
+}
+
 func (qc *queryCompiler) checkDeprecatedBuiltins(_ *QueryContext, body Body) (Body, error) {
 	errs := checkDeprecatedBuiltins(qc.compiler.deprecatedBuiltinsMap, body, qc.compiler.strict)
 	if len(errs) > 0 {
 		return nil, errs
 	}
 	return body, nil
 }
 
 func (qc *queryCompiler) rewriteWithModifiers(_ *QueryContext, body Body) (Body, error) {
 	f := newEqualityFactory(newLocalVarGenerator("q", body))
-	body, err := rewriteWithModifiersInBody(qc.compiler, f, body)
+	body, err := rewriteWithModifiersInBody(qc.compiler, qc.unsafeBuiltinsMap(), f, body)
 	if err != nil {
 		return nil, Errors{err}
 	}
 	return body, nil
 }
@@ -4783,14 +4784,14 @@ func rewriteDeclaredVar(g *localVarGenerator, stack *localDeclaredVars, v Var, o
 }
 
 // rewriteWithModifiersInBody will rewrite the body so that with modifiers do
 // not contain terms that require evaluation as values. If this function
 // encounters an invalid with modifier target then it will raise an error.
-func rewriteWithModifiersInBody(c *Compiler, f *equalityFactory, body Body) (Body, *Error) {
+func rewriteWithModifiersInBody(c *Compiler, unsafeBuiltinsMap map[string]struct{}, f *equalityFactory, body Body) (Body, *Error) {
 	var result Body
 	for i := range body {
-		exprs, err := rewriteWithModifier(c, f, body[i])
+		exprs, err := rewriteWithModifier(c, unsafeBuiltinsMap, f, body[i])
 		if err != nil {
 			return nil, err
 		}
 		if len(exprs) > 0 {
 			for _, expr := range exprs {
@@ -4801,15 +4802,15 @@ func rewriteWithModifiersInBody(c *Compiler, f *equalityFactory, body Body) (Bod
 		}
 	}
 	return result, nil
 }
 
-func rewriteWithModifier(c *Compiler, f *equalityFactory, expr *Expr) ([]*Expr, *Error) {
+func rewriteWithModifier(c *Compiler, unsafeBuiltinsMap map[string]struct{}, f *equalityFactory, expr *Expr) ([]*Expr, *Error) {
 
 	var result []*Expr
 	for i := range expr.With {
-		eval, err := validateWith(c, expr, i)
+		eval, err := validateWith(c, unsafeBuiltinsMap, expr, i)
 		if err != nil {
 			return nil, err
 		}
 
 		if eval {
@@ -4820,19 +4821,23 @@ func rewriteWithModifier(c *Compiler, f *equalityFactory, expr *Expr) ([]*Expr,
 	}
 
 	return append(result, expr), nil
 }
 
-func validateWith(c *Compiler, expr *Expr, i int) (bool, *Error) {
+func validateWith(c *Compiler, unsafeBuiltinsMap map[string]struct{}, expr *Expr, i int) (bool, *Error) {
 	target, value := expr.With[i].Target, expr.With[i].Value
 
 	// Ensure that values that are built-ins are rewritten to Ref (not Var)
 	if v, ok := value.Value.(Var); ok {
 		if _, ok := c.builtins[v.String()]; ok {
 			value.Value = Ref([]*Term{NewTerm(v)})
 		}
 	}
+	isBuiltinRefOrVar, err := isBuiltinRefOrVar(c.builtins, unsafeBuiltinsMap, target)
+	if err != nil {
+		return false, err
+	}
 
 	switch {
 	case isDataRef(target):
 		ref := target.Value.(Ref)
 		node := c.RuleTree
@@ -4852,19 +4857,19 @@ func validateWith(c *Compiler, expr *Expr, i int) (bool, *Error) {
 			// target is a function. It's probably wrong for arity-0 functions, but those are
 			// and edge case anyways.
 			if child := node.Child(ref[len(ref)-1].Value); child != nil {
 				for _, v := range child.Values {
 					if len(v.(*Rule).Head.Args) > 0 {
-						if validateWithFunctionValue(c.builtins, c.RuleTree, value) {
-							return false, nil
+						if ok, err := validateWithFunctionValue(c.builtins, unsafeBuiltinsMap, c.RuleTree, value); err != nil || ok {
+							return false, err // may be nil
 						}
 					}
 				}
 			}
 		}
 	case isInputRef(target): // ok, valid
-	case isBuiltinRefOrVar(c.builtins, target):
+	case isBuiltinRefOrVar:
 
 		// NOTE(sr): first we ensure that parsed Var builtins (`count`, `concat`, etc)
 		// are rewritten to their proper Ref convention
 		if v, ok := target.Value.(Var); ok {
 			target.Value = Ref([]*Term{NewTerm(v)})
@@ -4874,12 +4879,12 @@ func validateWith(c *Compiler, expr *Expr, i int) (bool, *Error) {
 		bi := c.builtins[targetRef.String()] // safe because isBuiltinRefOrVar checked this
 		if err := validateWithBuiltinTarget(bi, targetRef, target.Loc()); err != nil {
 			return false, err
 		}
 
-		if validateWithFunctionValue(c.builtins, c.RuleTree, value) {
-			return false, nil
+		if ok, err := validateWithFunctionValue(c.builtins, unsafeBuiltinsMap, c.RuleTree, value); err != nil || ok {
+			return false, err // may be nil
 		}
 	default:
 		return false, NewError(TypeErr, target.Location, "with keyword target must reference existing %v, %v, or a function", InputRootDocument, DefaultRootDocument)
 	}
 	return requiresEval(value), nil
@@ -4904,17 +4909,17 @@ func validateWithBuiltinTarget(bi *Builtin, target Ref, loc *location.Location)
 		return NewError(CompileErr, loc, "with keyword replacing built-in function: target must not be a void function")
 	}
 	return nil
 }
 
-func validateWithFunctionValue(bs map[string]*Builtin, ruleTree *TreeNode, value *Term) bool {
+func validateWithFunctionValue(bs map[string]*Builtin, unsafeMap map[string]struct{}, ruleTree *TreeNode, value *Term) (bool, *Error) {
 	if v, ok := value.Value.(Ref); ok {
 		if ruleTree.Find(v) != nil { // ref exists in rule tree
-			return true
+			return true, nil
 		}
 	}
-	return isBuiltinRefOrVar(bs, value)
+	return isBuiltinRefOrVar(bs, unsafeMap, value)
 }
 
 func isInputRef(term *Term) bool {
 	if ref, ok := term.Value.(Ref); ok {
 		if ref.HasPrefix(InputRootRef) {
@@ -4931,17 +4936,20 @@ func isDataRef(term *Term) bool {
 		}
 	}
 	return false
 }
 
-func isBuiltinRefOrVar(bs map[string]*Builtin, term *Term) bool {
+func isBuiltinRefOrVar(bs map[string]*Builtin, unsafeBuiltinsMap map[string]struct{}, term *Term) (bool, *Error) {
 	switch v := term.Value.(type) {
 	case Ref, Var:
+		if _, ok := unsafeBuiltinsMap[v.String()]; ok {
+			return false, NewError(CompileErr, term.Location, "with keyword replacing built-in function: target must not be unsafe: %q", v)
+		}
 		_, ok := bs[v.String()]
-		return ok
+		return ok, nil
 	}
-	return false
+	return false, nil
 }
 
 func isVirtual(node *TreeNode, ref Ref) bool {
 	for i := 0; i < len(ref); i++ {
 		child := node.Child(ref[i].Value)
diff --git a/ast/compile_test.go b/ast/compile_test.go
index 0bb3cc41c..aa3165114 100644
--- a/ast/compile_test.go
+++ b/ast/compile_test.go
@@ -4047,10 +4047,11 @@ func TestCompilerRewriteWithValue(t *testing.T) {
 	`
 
 	tests := []struct {
 		note         string
 		input        string
+		opts         func(*Compiler) *Compiler
 		expected     string
 		expectedRule *Rule
 		wantErr      error
 	}{
 		{
@@ -4152,10 +4153,30 @@ func TestCompilerRewriteWithValue(t *testing.T) {
 				r := MustParseRule(`p { true with http.send as count }`)
 				r.Body[0].With[0].Value.Value = Ref([]*Term{VarTerm("count")})
 				return r
 			}(),
 		},
+		{
+			note: "built-in function: replaced by another built-in that's marked unsafe",
+			input: `
+				q := is_object({"url": "https://httpbin.org", "method": "GET"})
+				p { q with is_object as http.send }
+			`,
+			opts:    func(c *Compiler) *Compiler { return c.WithUnsafeBuiltins(map[string]struct{}{"http.send": {}}) },
+			wantErr: fmt.Errorf("rego_compile_error: with keyword replacing built-in function: target must not be unsafe: \"http.send\""),
+		},
+		{
+			note: "non-built-in function: replaced by another built-in that's marked unsafe",
+			input: `
+			r(_) = {}
+			q := r({"url": "https://httpbin.org", "method": "GET"})
+			p {
+				q with r as http.send
+			}`,
+			opts:    func(c *Compiler) *Compiler { return c.WithUnsafeBuiltins(map[string]struct{}{"http.send": {}}) },
+			wantErr: fmt.Errorf("rego_compile_error: with keyword replacing built-in function: target must not be unsafe: \"http.send\""),
+		},
 		{
 			note: "built-in function: valid, arity 1, non-compound name",
 			input: `
 				p { concat("/", input) with concat as mock_concat }
 				mock_concat(_, _) = "foo/bar"
@@ -4169,10 +4190,13 @@ func TestCompilerRewriteWithValue(t *testing.T) {
 	}
 
 	for _, tc := range tests {
 		t.Run(tc.note, func(t *testing.T) {
 			c := NewCompiler()
+			if tc.opts != nil {
+				c = tc.opts(c)
+			}
 			module := fixture + tc.input
 			c.Modules["test"] = MustParseModule(module)
 			compileStages(c, c.rewriteWithModifiers)
 			if tc.wantErr == nil {
 				assertNotFailed(t, c)
@@ -6674,17 +6698,67 @@ func TestQueryCompilerWithStageAfterWithMetrics(t *testing.T) {
 		t.Error("Expected to have metrics after compiling")
 	}
 }
 
 func TestQueryCompilerWithUnsafeBuiltins(t *testing.T) {
-	c := NewCompiler().WithUnsafeBuiltins(map[string]struct{}{
-		"count": {},
-	})
+	tests := []struct {
+		note     string
+		query    string
+		compiler *Compiler
+		opts     func(QueryCompiler) QueryCompiler
+		err      string
+	}{
+		{
+			note:     "builtin unsafe via compiler",
+			query:    "count([])",
+			compiler: NewCompiler().WithUnsafeBuiltins(map[string]struct{}{"count": {}}),
+			err:      "unsafe built-in function calls in expression: count",
+		},
+		{
+			note:     "builtin unsafe via query compiler",
+			query:    "count([])",
+			compiler: NewCompiler(),
+			opts: func(qc QueryCompiler) QueryCompiler {
+				return qc.WithUnsafeBuiltins(map[string]struct{}{"count": {}})
+			},
+			err: "unsafe built-in function calls in expression: count",
+		},
+		{
+			note:     "builtin unsafe via compiler, 'with' mocking",
+			query:    "is_array([]) with is_array as count",
+			compiler: NewCompiler().WithUnsafeBuiltins(map[string]struct{}{"count": {}}),
+			err:      `with keyword replacing built-in function: target must not be unsafe: "count"`,
+		},
+		{
+			note:     "builtin unsafe via query compiler,  'with' mocking",
+			query:    "is_array([]) with is_array as count",
+			compiler: NewCompiler(),
+			opts: func(qc QueryCompiler) QueryCompiler {
+				return qc.WithUnsafeBuiltins(map[string]struct{}{"count": {}})
+			},
+			err: `with keyword replacing built-in function: target must not be unsafe: "count"`,
+		},
+	}
 
-	_, err := c.QueryCompiler().WithUnsafeBuiltins(map[string]struct{}{}).Compile(MustParseBody("count([])"))
-	if err != nil {
-		t.Fatal(err)
+	for _, tc := range tests {
+		t.Run(tc.note, func(t *testing.T) {
+			qc := tc.compiler.QueryCompiler()
+			if tc.opts != nil {
+				qc = tc.opts(qc)
+			}
+			_, err := qc.Compile(MustParseBody(tc.query))
+			var errs Errors
+			if !errors.As(err, &errs) {
+				t.Fatalf("expected error type %T, got %v %[2]T", errs, err)
+			}
+			if exp, act := 1, len(errs); exp != act {
+				t.Fatalf("expected %d error(s), got %d", exp, act)
+			}
+			if exp, act := tc.err, errs[0].Message; exp != act {
+				t.Errorf("expected message %q, got %q", exp, act)
+			}
+		})
 	}
 }
 
 func TestQueryCompilerWithDeprecatedBuiltins(t *testing.T) {
 	cases := []strictnessQueryTestCase{
diff --git a/rego/rego_test.go b/rego/rego_test.go
index 6f20c0502..40dd15f58 100644
--- a/rego/rego_test.go
+++ b/rego/rego_test.go
@@ -1435,10 +1435,11 @@ func TestModulePassing(t *testing.T) {
 func TestUnsafeBuiltins(t *testing.T) {
 
 	ctx := context.Background()
 
 	unsafeCountExpr := "unsafe built-in function calls in expression: count"
+	unsafeCountExprWith := `with keyword replacing built-in function: target must not be unsafe: "count"`
 
 	t.Run("unsafe query", func(t *testing.T) {
 		r := New(
 			Query(`count([1, 2, 3])`),
 			UnsafeBuiltins(map[string]struct{}{"count": {}}),
@@ -1446,10 +1447,20 @@ func TestUnsafeBuiltins(t *testing.T) {
 		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExpr) {
 			t.Fatalf("Expected unsafe built-in error but got %v", err)
 		}
 	})
 
+	t.Run("unsafe query, 'with' replacement", func(t *testing.T) {
+		r := New(
+			Query(`is_array([1, 2, 3]) with is_array as count`),
+			UnsafeBuiltins(map[string]struct{}{"count": {}}),
+		)
+		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExprWith) {
+			t.Fatalf("Expected unsafe built-in error but got %v", err)
+		}
+	})
+
 	t.Run("unsafe module", func(t *testing.T) {
 		r := New(
 			Query(`data.pkg.deny`),
 			Module("pkg.rego", `package pkg
 			deny {
@@ -1461,20 +1472,60 @@ func TestUnsafeBuiltins(t *testing.T) {
 		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExpr) {
 			t.Fatalf("Expected unsafe built-in error but got %v", err)
 		}
 	})
 
+	t.Run("unsafe module, 'with' replacement in query", func(t *testing.T) {
+		r := New(
+			Query(`data.pkg.deny with is_array as count`),
+			Module("pkg.rego", `package pkg
+			deny {
+				is_array(input.requests) > 10
+			}
+			`),
+			UnsafeBuiltins(map[string]struct{}{"count": {}}),
+		)
+		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExprWith) {
+			t.Fatalf("Expected unsafe built-in error but got %v", err)
+		}
+	})
+
+	t.Run("unsafe module, 'with' replacement in module", func(t *testing.T) {
+		r := New(
+			Query(`data.pkg.deny`),
+			Module("pkg.rego", `package pkg
+			deny {
+				is_array(input.requests) > 10 with is_array as count
+			}
+			`),
+			UnsafeBuiltins(map[string]struct{}{"count": {}}),
+		)
+		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExprWith) {
+			t.Fatalf("Expected unsafe built-in error but got %v", err)
+		}
+	})
+
 	t.Run("inherit in query", func(t *testing.T) {
 		r := New(
 			Compiler(ast.NewCompiler().WithUnsafeBuiltins(map[string]struct{}{"count": {}})),
 			Query("count([])"),
 		)
 		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExpr) {
 			t.Fatalf("Expected unsafe built-in error but got %v", err)
 		}
 	})
 
+	t.Run("inherit in query, 'with' replacement", func(t *testing.T) {
+		r := New(
+			Compiler(ast.NewCompiler().WithUnsafeBuiltins(map[string]struct{}{"count": {}})),
+			Query("is_array([]) with is_array as count"),
+		)
+		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExprWith) {
+			t.Fatalf("Expected unsafe built-in error but got %v", err)
+		}
+	})
+
 	t.Run("override/disable in query", func(t *testing.T) {
 		r := New(
 			Compiler(ast.NewCompiler().WithUnsafeBuiltins(map[string]struct{}{"count": {}})),
 			UnsafeBuiltins(map[string]struct{}{}),
 			Query("count([])"),

Vulnerable code: 			body, err := rewriteWithModifiersInBody(c, f, body)
	var unsafe map[string]struct{}
	if qc.unsafeBuiltins != nil {
		unsafe = qc.unsafeBuiltins
	} else {
		unsafe = qc.compiler.unsafeBuiltinsMap
	}
	errs := checkUnsafeBuiltins(unsafe, body)
	body, err := rewriteWithModifiersInBody(qc.compiler, f, body)
func rewriteWithModifiersInBody(c *Compiler, f *equalityFactory, body Body) (Body, *Error) {
		exprs, err := rewriteWithModifier(c, f, body[i])
func rewriteWithModifier(c *Compiler, f *equalityFactory, expr *Expr) ([]*Expr, *Error) {
		eval, err := validateWith(c, expr, i)
func validateWith(c *Compiler, expr *Expr, i int) (bool, *Error) {
						if validateWithFunctionValue(c.builtins, c.RuleTree, value) {
							return false, nil
	case isBuiltinRefOrVar(c.builtins, target):
		if validateWithFunctionValue(c.builtins, c.RuleTree, value) {
			return false, nil
func validateWithFunctionValue(bs map[string]*Builtin, ruleTree *TreeNode, value *Term) bool {
			return true
	return isBuiltinRefOrVar(bs, value)
func isBuiltinRefOrVar(bs map[string]*Builtin, term *Term) bool {
		return ok
	return false
	c := NewCompiler().WithUnsafeBuiltins(map[string]struct{}{
		"count": {},
	})
	_, err := c.QueryCompiler().WithUnsafeBuiltins(map[string]struct{}{}).Compile(MustParseBody("count([])"))
	if err != nil {
		t.Fatal(err)
Fixed code: 			body, err := rewriteWithModifiersInBody(c, c.unsafeBuiltinsMap, f, body)
	errs := checkUnsafeBuiltins(qc.unsafeBuiltinsMap(), body)
func (qc *queryCompiler) unsafeBuiltinsMap() map[string]struct{} {
	if qc.unsafeBuiltins != nil {
		return qc.unsafeBuiltins
	}
	return qc.compiler.unsafeBuiltinsMap
}

	body, err := rewriteWithModifiersInBody(qc.compiler, qc.unsafeBuiltinsMap(), f, body)
func rewriteWithModifiersInBody(c *Compiler, unsafeBuiltinsMap map[string]struct{}, f *equalityFactory, body Body) (Body, *Error) {
		exprs, err := rewriteWithModifier(c, unsafeBuiltinsMap, f, body[i])
func rewriteWithModifier(c *Compiler, unsafeBuiltinsMap map[string]struct{}, f *equalityFactory, expr *Expr) ([]*Expr, *Error) {
		eval, err := validateWith(c, unsafeBuiltinsMap, expr, i)
func validateWith(c *Compiler, unsafeBuiltinsMap map[string]struct{}, expr *Expr, i int) (bool, *Error) {
	isBuiltinRefOrVar, err := isBuiltinRefOrVar(c.builtins, unsafeBuiltinsMap, target)
	if err != nil {
		return false, err
	}
						if ok, err := validateWithFunctionValue(c.builtins, unsafeBuiltinsMap, c.RuleTree, value); err != nil || ok {
							return false, err // may be nil
	case isBuiltinRefOrVar:
		if ok, err := validateWithFunctionValue(c.builtins, unsafeBuiltinsMap, c.RuleTree, value); err != nil || ok {
			return false, err // may be nil
func validateWithFunctionValue(bs map[string]*Builtin, unsafeMap map[string]struct{}, ruleTree *TreeNode, value *Term) (bool, *Error) {
			return true, nil
	return isBuiltinRefOrVar(bs, unsafeMap, value)
func isBuiltinRefOrVar(bs map[string]*Builtin, unsafeBuiltinsMap map[string]struct{}, term *Term) (bool, *Error) {
		if _, ok := unsafeBuiltinsMap[v.String()]; ok {
			return false, NewError(CompileErr, term.Location, "with keyword replacing built-in function: target must not be unsafe: %q", v)
		}
		return ok, nil
	return false, nil
		opts         func(*Compiler) *Compiler
		{
			note: "built-in function: replaced by another built-in that's marked unsafe",
			input: `
				q := is_object({"url": "https://httpbin.org", "method": "GET"})
				p { q with is_object as http.send }
			`,
			opts:    func(c *Compiler) *Compiler { return c.WithUnsafeBuiltins(map[string]struct{}{"http.send": {}}) },
			wantErr: fmt.Errorf("rego_compile_error: with keyword replacing built-in function: target must not be unsafe: \"http.send\""),
		},
		{
			note: "non-built-in function: replaced by another built-in that's marked unsafe",
			input: `
			r(_) = {}
			q := r({"url": "https://httpbin.org", "method": "GET"})
			p {
				q with r as http.send
			}`,
			opts:    func(c *Compiler) *Compiler { return c.WithUnsafeBuiltins(map[string]struct{}{"http.send": {}}) },
			wantErr: fmt.Errorf("rego_compile_error: with keyword replacing built-in function: target must not be unsafe: \"http.send\""),
		},
			if tc.opts != nil {
				c = tc.opts(c)
			}
	tests := []struct {
		note     string
		query    string
		compiler *Compiler
		opts     func(QueryCompiler) QueryCompiler
		err      string
	}{
		{
			note:     "builtin unsafe via compiler",
			query:    "count([])",
			compiler: NewCompiler().WithUnsafeBuiltins(map[string]struct{}{"count": {}}),
			err:      "unsafe built-in function calls in expression: count",
		},
		{
			note:     "builtin unsafe via query compiler",
			query:    "count([])",
			compiler: NewCompiler(),
			opts: func(qc QueryCompiler) QueryCompiler {
				return qc.WithUnsafeBuiltins(map[string]struct{}{"count": {}})
			},
			err: "unsafe built-in function calls in expression: count",
		},
		{
			note:     "builtin unsafe via compiler, 'with' mocking",
			query:    "is_array([]) with is_array as count",
			compiler: NewCompiler().WithUnsafeBuiltins(map[string]struct{}{"count": {}}),
			err:      `with keyword replacing built-in function: target must not be unsafe: "count"`,
		},
		{
			note:     "builtin unsafe via query compiler,  'with' mocking",
			query:    "is_array([]) with is_array as count",
			compiler: NewCompiler(),
			opts: func(qc QueryCompiler) QueryCompiler {
				return qc.WithUnsafeBuiltins(map[string]struct{}{"count": {}})
			},
			err: `with keyword replacing built-in function: target must not be unsafe: "count"`,
		},
	}
	for _, tc := range tests {
		t.Run(tc.note, func(t *testing.T) {
			qc := tc.compiler.QueryCompiler()
			if tc.opts != nil {
				qc = tc.opts(qc)
			}
			_, err := qc.Compile(MustParseBody(tc.query))
			var errs Errors
			if !errors.As(err, &errs) {
				t.Fatalf("expected error type %T, got %v %[2]T", errs, err)
			}
			if exp, act := 1, len(errs); exp != act {
				t.Fatalf("expected %d error(s), got %d", exp, act)
			}
			if exp, act := tc.err, errs[0].Message; exp != act {
				t.Errorf("expected message %q, got %q", exp, act)
			}
		})
	unsafeCountExprWith := `with keyword replacing built-in function: target must not be unsafe: "count"`
	t.Run("unsafe query, 'with' replacement", func(t *testing.T) {
		r := New(
			Query(`is_array([1, 2, 3]) with is_array as count`),
			UnsafeBuiltins(map[string]struct{}{"count": {}}),
		)
		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExprWith) {
			t.Fatalf("Expected unsafe built-in error but got %v", err)
		}
	})

	t.Run("unsafe module, 'with' replacement in query", func(t *testing.T) {
		r := New(
			Query(`data.pkg.deny with is_array as count`),
			Module("pkg.rego", `package pkg
			deny {
				is_array(input.requests) > 10
			}
			`),
			UnsafeBuiltins(map[string]struct{}{"count": {}}),
		)
		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExprWith) {
			t.Fatalf("Expected unsafe built-in error but got %v", err)
		}
	})

	t.Run("unsafe module, 'with' replacement in module", func(t *testing.T) {
		r := New(
			Query(`data.pkg.deny`),
			Module("pkg.rego", `package pkg
			deny {
				is_array(input.requests) > 10 with is_array as count
			}
			`),
			UnsafeBuiltins(map[string]struct{}{"count": {}}),
		)
		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExprWith) {
			t.Fatalf("Expected unsafe built-in error but got %v", err)
		}
	})

	t.Run("inherit in query, 'with' replacement", func(t *testing.T) {
		r := New(
			Compiler(ast.NewCompiler().WithUnsafeBuiltins(map[string]struct{}{"count": {}})),
			Query("is_array([]) with is_array as count"),
		)
		if _, err := r.Eval(ctx); err == nil || !strings.Contains(err.Error(), unsafeCountExprWith) {
			t.Fatalf("Expected unsafe built-in error but got %v", err)
		}
	})