Skip to content

Custom rules: your own runtime protection rules

Custom rules let you add your own checks to runtime protection without waiting for an SDK release. For example, you can block an admin route from outside your office network, or alert when a specific header shows up.

Each rule is a short expression in a small CEL-style language. Rules reach the app inside its signed config, so they are trusted only as far as the key you pinned. The SDK parses and evaluates them as data. It never runs downloaded code.

All seven SDKs (TypeScript, Python, Go, .NET, Java, PHP and Rust) enforce custom rules and pass the same shared test corpus.

Build a rule without writing an expression

Section titled “Build a rule without writing an expression”

Go to WAF & RASP → RASP, choose an app, and find Custom rules.

  1. Pick a starting point, such as Lock down an admin path, or select Add rule.
  2. Fill in the sentence: what to check, how to compare it, and the value. Every condition must match.
  3. Choose the action: Block stops the request and records it, Alert records it and sends an alert, and Monitor only records it.
  4. Choose the apps it applies to.
  5. Use Try it to check a method, path and IP address. Nothing is sent.
  6. Select Save rules.

The builder writes an ordinary expression, shown under Advanced. A rule that uses ||, parentheses or regular expressions can be edited only there.

You can also select Create a rule on a group in the decision queue to start from what your app’s SDK reported. Shield360 Edge does not run custom rules, so Edge groups do not offer this.

  1. Open WAF & RASP → RASP. The rules belong to the environment, so you do not need to choose an app.
  2. Scroll to Custom rules.
  3. Add a rule. Give it a name, write its expression, choose its action and, if you like, the apps it applies to.
  4. Use Test against a sample to try the rule against a sample request in your browser. The editor checks the expression as you type.
  5. Save. A save is all or nothing: if any rule is invalid, nothing is saved and the invalid rule is named.

Rules apply to every app in the environment unless you limit them to some apps. You can reorder, turn off and remove rules.

ActionWhat happens on a match
blockThe request gets a 403, but only when the custom_rule attack type is in block mode for that app. Otherwise it is recorded as monitored.
monitorThe match is recorded as a custom_rule event, with no alert.
alertThe match is recorded and raises a warning-level alert.

A match is an event of attack type custom_rule with the rule custom.<id>, and appears with the app’s other runtime protection events.

// Block the admin API from outside the office network
request.path.startsWith("/admin") && !request.ip.inCidr("203.0.113.0/24")
// Alert on a debug header in production traffic
request.headers["x-debug"] != null
// Block any input that looks like a path to the cloud metadata service
input.value.contains("169.254.169.254")
// Block writes to the orders route except from known methods
app.name == "orders-api" && request.route == "/orders/:id" && !(request.method in ["GET", "HEAD", "OPTIONS"])

Learning mode suggests rules like the last one from your apps’ normal traffic.

FieldMeaning
request.method, request.path, request.route, request.ipThe request. route is the framework’s route template.
request.headers["name"]A header. The name is case-insensitive.
request.query["k"], request.body["a.b"]A query parameter, or a field of the parsed body.
input.value, input.key, input.sourceEach untrusted input. The rule is evaluated once per input and matches if any input makes it true.
sink.name, attack.type, attack.ruleWhat a built-in check found. A rule that reads these runs each time a built-in check fires.
app.nameThe app.

Any field can be null.

  • Comparisons: ==, !=, <, <=, >, >= (strings compare by code point).
  • Lists: x in ["a", "b"].
  • Logic: &&, ||, !.
  • Methods: startsWith, endsWith, contains, matches, inCidr, lowerAscii(). Each takes one string literal. Raw strings r"…" are allowed.

There are no numbers, loops, functions or I/O. Types are checked when the rule is saved.

matches uses a strict subset of regular expressions that runs in linear time: no backreferences, lookaround, named groups, inline flags or nested quantifiers. \d, \w and \s are ASCII only, and matches reads the first 4096 characters.

These are the same on the platform and in every SDK:

LimitValue
Expression2048 bytes
String literal1024 bytes
Items in an in list64
Expression depth / parenthesis nesting12 / 12
Nodes128
Regular expressions per rule8, each at most 256 bytes
Rules per environment64 KiB of JSON in total
  • A rule that reads sink.* or attack.* runs each time a built-in check fires.
  • Every other rule runs with the virtual patches, before your handler: once on the request head and again when the body is available. A rule that reads request.body runs only once the framework reports a body.
  • An invalid rule is skipped with one warning and never breaks the config. Evaluation fails open, with a work budget per request.

Viewing rules needs View data protection events. Changing them needs Manage agent policies, checked on the server. Every change is recorded in the audit log as a summary of the ids added, removed and changed, never the expression text.