Summary
cel.AstToString produces a syntactically invalid CEL expression when
the resulting double literal is formatted in scientific notation by
strconv.FormatFloat with 'g' and precision -1 (i.e., for any
double of magnitude < 1e-4 or >= 1e+21). The unparser appends .0
to any formatted double that lacks a . so that it parses as a double
rather than an int, but it does not consider that scientific notation
already produces a valid double literal — appending .0 to 1e-05
produces 1e-05.0, which is rejected by the parser.
Reported via Google OSS-VRP and classified as a product vulnerability
under OT2/OT3 (non-bounty); filing here per the triage team's
suggestion.
Affected code
parser/unparser.go lines 296-302:
case types.Double:
// represent the float using the minimum required digits
d := strconv.FormatFloat(float64(val), 'g', -1, 64)
un.str.WriteString(d)
if !strings.Contains(d, ".") {
un.str.WriteString(".0")
}
Reproducer
package main
import (
"fmt"
"github.com/google/cel-go/cel"
)
func main() {
env, _ := cel.NewEnv()
for _, src := range []string{"0.00001", "0.000000001", "1e30", "5e-5", "1E7"} {
ast, iss := env.Parse(src)
if iss != nil && iss.Err() != nil {
fmt.Printf("first parse of %q failed: %v\n", src, iss.Err())
continue
}
s2, _ := cel.AstToString(ast)
_, iss = env.Parse(s2)
fmt.Printf("%-20s -> AstToString=%-15s reparse=", src, fmt.Sprintf("%q", s2))
if iss == nil || iss.Err() == nil {
fmt.Println("OK")
} else {
fmt.Println("FAIL:", iss.Err())
}
}
}
Output against v0.28.1:
"0.00001" -> AstToString="1e-05.0" reparse=FAIL: ERROR: <input>:1:6: Syntax error: extraneous input '.0' expecting <EOF>
"0.000000001" -> AstToString="1e-09.0" reparse=FAIL
"1e30" -> AstToString="1e+30.0" reparse=FAIL
"5e-5" -> AstToString="5e-05.0" reparse=FAIL
"1E7" -> AstToString="1e+07.0" reparse=FAIL
For comparison, doubles inside the 'g' fixed-format range round-trip
correctly (no bug):
"0.0001" -> AstToString="0.0001" reparse=OK
"1.5" -> AstToString="1.5" reparse=OK
Versions confirmed vulnerable
- v0.28.1 (latest released at time of report)
- master @ commit
a63c50303c3aec3836b78831c61ad3b0f66ea1ad (2026-05-18)
Suggested fix
Treat scientific notation as also being a valid CEL double literal —
only append .0 if the formatted string has neither a . nor an
exponent marker:
case types.Double:
d := strconv.FormatFloat(float64(val), 'g', -1, 64)
un.str.WriteString(d)
if !strings.ContainsAny(d, ".eE") {
un.str.WriteString(".0")
}
Discovered via differential fuzzing using a Parse → AstToString → Parse
roundtrip oracle. Happy to send a PR if useful — let me know.
Summary
cel.AstToStringproduces a syntactically invalid CEL expression whenthe resulting double literal is formatted in scientific notation by
strconv.FormatFloatwith'g'and precision-1(i.e., for anydouble of magnitude
< 1e-4or>= 1e+21). The unparser appends.0to any formatted double that lacks a
.so that it parses as adoublerather than an
int, but it does not consider that scientific notationalready produces a valid double literal — appending
.0to1e-05produces
1e-05.0, which is rejected by the parser.Reported via Google OSS-VRP and classified as a product vulnerability
under OT2/OT3 (non-bounty); filing here per the triage team's
suggestion.
Affected code
parser/unparser.golines 296-302:Reproducer
Output against
v0.28.1:For comparison, doubles inside the
'g'fixed-format range round-tripcorrectly (no bug):
Versions confirmed vulnerable
a63c50303c3aec3836b78831c61ad3b0f66ea1ad(2026-05-18)Suggested fix
Treat scientific notation as also being a valid CEL double literal —
only append
.0if the formatted string has neither a.nor anexponent marker:
Discovered via differential fuzzing using a
Parse → AstToString → Parseroundtrip oracle. Happy to send a PR if useful — let me know.