Skip to content

CEL Policy Matching Reference

CEL policy matching is the shared request matcher used by WAF rules, rate-limit rules, traffic shaper rules, and cache rules.

What It Is

match_rule is a request-only policy matcher. It can be configured with the management UI Builder mode or with raw CEL in CEL mode.

An empty or omitted match_rule matches any request.

Builder mode generates a CEL expression from grouped conditions. For non-empty builder rules, p2pstream stores both the structured builder and the generated cel_expression so the API can validate that both describe the same rule.

Where It Runs

Policy match timing depends on the feature using the matcher:

FeatureMatch timing
WAFBefore rate limits, traffic shapers, route resolution, and forwarding.
Rate limitsAfter WAF, before traffic shaping and route resolution.
Traffic shapersAfter WAF and rate limits, before route/target forwarding.
CacheAfter route/target selection, before upstream forwarding.

Cache route_ids and target_ids are separate post-routing filters. They are not CEL variables.

API Shape

PublicPolicyMatchRule has two fields:

FieldUse
cel_expressionThe CEL expression evaluated for each request.
builderStructured groups and conditions that can generate a CEL expression.

When both fields are supplied, cel_expression must exactly match the expression generated from builder.

Legacy match fields are removed from the public API. Existing stored legacy rows are migrated automatically to CEL/builder JSON.

Variables

VariableTypeNotes
methodstringUppercase request method, such as GET or POST.
protocolstringListener protocol, http or https.
hoststringNormalized request host without port.
pathstringURL path.
remote_ipstringClient remote IP parsed from the connection remote address.
headersmap string to list stringHeader names are lowercase; repeated values are preserved.
cookiesmap string to stringFirst cookie value by name.
querymap string to list stringQuery parameter values by name.

Helper Functions

host_match(host, pattern) checks an exact or wildcard host pattern.

text
host_match(host, "app.example.com")
host_match(host, "*.example.com")

path_prefix(path, prefix) checks a p2pstream path prefix. The prefix must start with /.

text
path_prefix(path, "/api")
path_prefix(path, "/assets")

cidr(remote_ip, cidr) checks whether remote_ip is inside a valid IP prefix. Invalid IPs evaluate false. Literal invalid CIDR values are rejected during validation when p2pstream can see them statically.

text
cidr(remote_ip, "198.51.100.0/24")
cidr(remote_ip, "2001:db8::/32")

Common CEL Operators

ExpressionMeaning
method == "POST"Exact string equality.
method in ["GET", "HEAD"]Value is one of a list.
path.startsWith("/api/")String prefix check.
path.endsWith(".css")String suffix check.
host.contains("example")String contains check.
path.matches("^/assets/.+\\.js$")Regex match.
"x-plan" in headersHeader is present. Header names are lowercase.
headers["x-plan"].exists(v, v == "free")Any repeated header value matches.
query["version"].exists(v, v in ["v1", "v2"])Any query value matches.
!("session" in cookies)Cookie is absent.
A && BBoth expressions must be true.
`A
!(A)Expression must be false.

Builder Mode Mapping

Builder mode generates CEL from groups and conditions:

Builder itemCEL behavior
All groupJoins child expressions with &&.
Any groupJoins child expressions with `
Not group or conditionWraps the generated expression with !().
Header conditionChecks all repeated header values.
Query conditionChecks all repeated query values.
Cookie conditionChecks the first cookie value by name.
Present operatorValid only for header, cookie, and query fields.
CIDR operatorValid only for remote IP fields.
Host pattern operatorValid only for host fields.
Path prefix operatorValues must start with /.

Header and query conditions check all repeated values. Internally migrated legacy rules can preserve first-value behavior for old stored rows.

Limits And Validation

LimitValue
CEL expression max size4096 bytes
Builder max groups and conditions64 total nodes
Condition max values64
Condition value max size512 bytes
CEL evaluation cost limit20000

Expressions must compile and evaluate to bool. Regex literals are validated when p2pstream can see them statically.

Literal arguments receive targeted validation:

  • cidr(remote_ip, "...") requires a valid CIDR prefix.
  • path_prefix(path, "...") requires a prefix starting with /.
  • host_match(host, "...") requires a non-empty host pattern.

Examples

Login POST protection:

text
method == "POST" && host_match(host, "app.example.com") && path_prefix(path, "/login")

Static asset cache matching:

text
method in ["GET", "HEAD"] && host_match(host, "app.example.com") && path.matches("^/assets/.+\\.(css|js|png|webp|svg|woff2)$")

Header or query tier selection:

text
headers["x-plan"].exists(v, v == "free") || query["tier"].exists(v, v == "free")

Bot user-agent match:

text
headers["user-agent"].exists(v, v.matches("(?i)(bot|crawler)"))

Cookie absence:

text
!("session" in cookies)

IPv4 CIDR:

text
cidr(remote_ip, "198.51.100.0/24")

IPv6 CIDR:

text
cidr(remote_ip, "2001:db8::/32")

Combined host, path, and method rule:

text
method in ["GET", "POST"] && host_match(host, "*.example.com") && path_prefix(path, "/api")

What CEL Cannot See

CEL policy matches cannot inspect:

  • route data,
  • target data,
  • target health,
  • load-balancer state,
  • response data,
  • cache result,
  • request body.

For cache rules, route and target scoping must use the rule's route_ids and target_ids filters instead of CEL.

Troubleshooting

SymptomFix
Rule matches every requestCheck whether match_rule is empty or the builder has no conditions. Empty matches mean any request.
Header rule does not matchUse lowercase header names such as headers["x-plan"]; p2pstream lowercases header keys.
Header or query value rule does not matchUse .exists(v, ...) because headers and query parameters are lists.
Regex is rejectedCompile the regex separately and escape backslashes correctly inside the CEL string.
Path prefix is rejectedPrefix values for path_prefix and builder path-prefix conditions must start with /.
CIDR is rejected or never matchesUse a valid CIDR prefix and confirm p2pstream sees the expected client IP.
Route or target data is missingCEL only sees request data. Use feature-specific route/target filters where available.

Operations documentation for self-hosted p2pstream deployments.