Describe the bug
Since v0.25.0, enabling cel.OptTrackState (or cel.OptExhaustiveEval) together with cel.CostLimit / cel.CostTracking silently disables runtime cost tracking:
- the cost limit is never enforced. Evaluation completes normally instead of returning
EvalCancelledError{Cause: CostLimitExceeded};
EvalDetails.ActualCost() returns a non-nil pointer to 0 instead of the real cost.
There is no error, no warning, and no API-level hint that the limit has been disabled. Code that relies on CostLimit as a DoS guard keeps running with the guard removed.
To Reproduce
Check which components this affects:
Sample expression and input that reproduces the issue:
Test setup:
package main
import (
"fmt"
"github.com/google/cel-go/cel"
)
func main() {
env, err := cel.NewEnv(cel.Variable("a", cel.IntType), cel.Variable("b", cel.IntType))
if err != nil {
panic(err)
}
ast, iss := env.Compile("a + b > 47")
if iss.Err() != nil {
panic(iss.Err())
}
cases := []struct {
name string
opts []cel.ProgramOption
}{
{"CostLimit(1)", []cel.ProgramOption{cel.CostLimit(1)}},
{"CostLimit(1)+OptTrackState", []cel.ProgramOption{cel.EvalOptions(cel.OptTrackState), cel.CostLimit(1)}},
{"CostLimit(1)+OptExhaustiveEval", []cel.ProgramOption{cel.EvalOptions(cel.OptExhaustiveEval), cel.CostLimit(1)}},
{"CostLimit(1)+OptPartialEval", []cel.ProgramOption{cel.EvalOptions(cel.OptPartialEval), cel.CostLimit(1)}},
{"OptTrackCost", []cel.ProgramOption{cel.EvalOptions(cel.OptTrackCost)}},
{"OptTrackCost+OptTrackState", []cel.ProgramOption{cel.EvalOptions(cel.OptTrackCost | cel.OptTrackState)}},
}
for _, tc := range cases {
prg, err := env.Program(ast, tc.opts...)
if err != nil {
panic(err)
}
_, det, evalErr := prg.Eval(map[string]any{"a": 42, "b": 4})
cost := "<nil>"
if det != nil && det.ActualCost() != nil {
cost = fmt.Sprint(*det.ActualCost())
}
fmt.Printf("%-34s err=%-46v actualCost=%s\n", tc.name, evalErr, cost)
}
}
Expected behavior
CostLimit(1) err=operation cancelled: actual cost limit exceeded actualCost=2
CostLimit(1)+OptTrackState err=operation cancelled: actual cost limit exceeded actualCost=2
CostLimit(1)+OptExhaustiveEval err=operation cancelled: actual cost limit exceeded actualCost=2
CostLimit(1)+OptPartialEval err=operation cancelled: actual cost limit exceeded actualCost=2
OptTrackCost err=<nil> actualCost=4
OptTrackCost+OptTrackState err=<nil> actualCost=4
Actual behavior
CostLimit(1) err=operation cancelled: actual cost limit exceeded actualCost=2
CostLimit(1)+OptTrackState err=<nil> actualCost=0 <-- BUG
CostLimit(1)+OptExhaustiveEval err=<nil> actualCost=0 <-- BUG
CostLimit(1)+OptPartialEval err=operation cancelled: actual cost limit exceeded actualCost=2
OptTrackCost err=<nil> actualCost=4
OptTrackCost+OptTrackState err=<nil> actualCost=0 <-- BUG
Additional context
First seen in https://github.com/authzed/spicedb/actions/runs/32164309872/job/95800380925?pr=3269#step:4:102
Describe the bug
Since v0.25.0, enabling
cel.OptTrackState(orcel.OptExhaustiveEval) together withcel.CostLimit/cel.CostTrackingsilently disables runtime cost tracking:EvalCancelledError{Cause: CostLimitExceeded};EvalDetails.ActualCost()returns a non-nil pointer to0instead of the real cost.There is no error, no warning, and no API-level hint that the limit has been disabled. Code that relies on
CostLimitas a DoS guard keeps running with the guard removed.To Reproduce
Check which components this affects:
Sample expression and input that reproduces the issue:
Test setup:
Expected behavior
Actual behavior
Additional context
First seen in https://github.com/authzed/spicedb/actions/runs/32164309872/job/95800380925?pr=3269#step:4:102