// runtime guardrails
Rule language
Write Runtime Guardrails rules directly in YAML with CEL expressions, normalized event fields, parsed shell commands, and sequences.
You can write Runtime Guardrails rules directly in the YAML editor. The rule assistant is optional.
- Open Agents → Rules.
- Select New rule.
- Replace the commented example in the editor with your rule.
- Choose the client groups that should receive it and set the rule to Monitor, Enforce, or Disabled.
- Select Save.
Superagent validates the YAML structure and compiles the CEL expression before saving. Each endpoint validates the rule again before activation.
Start with a complete rule
This rule detects reads of .env files while ignoring .env.example:
id: acme.secrets.env_read
version: "1.0"
enabled: true
title: Environment file read
description: |-
Detects a requested read of an environment file that may contain credentials.
It does not treat example environment files as secrets.
severity: high
tags:
- secrets
- attack.t1552.001
expr: |-
event.event_type == "file.read" &&
event.file_path.matches("(^|/)\\.env(\\.[^/]+)?$") &&
!event.file_path.endsWith(".env.example")
enforce: falseUse an organization-specific prefix such as acme. for custom rule IDs. Increment version whenever the rule logic changes so findings show which policy produced the match.
Top-level fields
A rule is one YAML object, up to 128 KiB of UTF-8 text. Do not use YAML aliases, wrap it in rules:, place multiple rules in one document, or add unknown top-level fields.
| Field | Required | Description |
|---|---|---|
id |
Yes | Stable, dot-separated identifier, up to 128 characters |
version |
Yes | Rule-owned version string copied into findings |
title |
Yes | Human-readable finding title |
severity |
Yes | info, low, medium, high, or critical |
expr |
One of | CEL predicate evaluated against one event |
sequence |
One of | Ordered multi-event detection |
description |
No | Operator-facing explanation of what a match proves |
tags |
No | Categories copied into findings |
enabled |
No | Enables the rule; defaults to true |
enforce |
No | Makes eligible matches blockable; defaults to false |
deny_message |
No | Control-free, single-line message returned for a denied action, up to 512 UTF-8 bytes |
Define exactly one of expr or sequence. Superagent recommends including description, tags, and an explicit enabled value even though the rule engine can supply their defaults.
Rule IDs use lowercase letters, digits, underscores, and dashes in dot-separated segments. Each segment must start and end with a letter or digit.
The Mode control in the editor writes enabled and enforce when you save. Use that control when changing a rule's operating mode.
Write CEL expressions
Every expr must return true or false. Start by selecting the event types the rule understands, then add the conditions that identify the behavior.
event.event_type == "file.write" &&
event.file_path.startsWith("/etc/")Common CEL operations include:
| Operation | Example |
|---|---|
| Boolean logic | a && b, `a |
| Equality | a == b, a != b |
| Membership | value in ["a", "b"] |
| String tests | contains(), startsWith(), endsWith(), matches() |
| List predicates | exists(), all(), exists_one() |
| List ranges | items.slice(start, end) |
| Integer indexes | lists.range(n).exists(i, ...) |
| Missing nullable value | event.exit_code == null |
matches() uses RE2 regular expressions. CEL strings require escaping, so a literal dot is written as "\\.env".
Use direct field access such as event.command. Unknown fields and computed field access are rejected.
Choose the right input
Rules can evaluate two inputs:
eventis the normalized action and context recorded across supported coding agents.shell_commandsis a parsed, rule-only view of commands found inevent.command.
Choose the simplest input that preserves the distinction you need:
| What you need to match | Input |
|---|---|
| File, URL, MCP, permission, model, or other normalized data | event |
| Literal text in the original command | event.command |
| Executable names, arguments, pipelines, redirects, or wrappers | shell_commands |
| Ordered activity across multiple events | sequence |
Raw command text can match comments, quoted examples, or here-document content. Prefer shell_commands when a rule depends on what the shell will execute, especially for enforcement rules.
Normalized events
Actions use the most specific available event type. A recognized shell request is command.exec, not both command.exec and tool.call. tool.call is the fallback when the action cannot be classified more specifically.
Common event types and their principal fields are:
| Event type | Meaning | Principal fields |
|---|---|---|
command.exec |
Requested shell or process action | command, tool_name, tool_call_id |
command.result |
Observed command result | command, exit_code, duration_ms, tool_call_id |
file.read |
Requested file access | file_path, tool_name, tool_call_id |
file.write |
Requested or observed file change | file_path, diff_sha256, diff_bytes |
file.delete |
Requested or observed file deletion | file_path, diff_sha256, diff_bytes |
network.indicator |
Known web or network target | url, mcp_server, mcp_tool |
tool.call |
Other or unknown tool request | tool_name, mcp_server, mcp_tool |
tool.result |
Result of a generic tool call | tool_name, tool_call_id |
permission.requested |
Permission prompt | approval_required, approval_reason |
permission.approved |
Approved permission request | approval_decision, approval_reason |
permission.denied |
Denied permission request | approval_decision, approval_reason |
config.agent |
Observed agent configuration | Context fields |
config.mcp |
Observed MCP configuration | mcp_server, mcp_tool |
session.start, session.end |
Session boundary | Context fields |
prompt.user |
User prompt | content_preview |
message.assistant, message.reasoning |
Agent output | content_preview |
Context fields are available on every event type:
event.actor
event.cli_version
event.confidence
event.content_preview
event.entrypoint
event.git_branch
event.model
event.model_provider
event.project_path
event.session_id
event.source_agent
event.source_type
event.sub_agent
event.tags
event.timestampAll event keys are present during evaluation. Missing strings are "", missing diff_bytes is 0, and missing tags are []. The nullable fields approval_required, duration_ms, and exit_code are null when unavailable.
Pre-action events describe requested behavior, not confirmed execution. A later result may be linked by tool_call_id, but some integrations record only one side.
For command activity, hooks and artifacts normally provide command.exec. Some OpenTelemetry sources expose only command.result, so a rule that needs both capture paths can use:
event.event_type == "command.exec" ||
(event.source_type == "otel" && event.event_type == "command.result")The source_type condition prevents the rule from matching both the request and result on sources that emit both.
Parse executable commands
Use shell_commands to reason about executable behavior instead of matching raw text:
id: acme.network.curl_upload
version: "1.0"
enabled: true
title: Curl file upload requested
description: Detects curl commands that request a local file upload.
severity: high
tags:
- exfiltration
expr: |-
event.event_type == "command.exec" &&
shell_commands.exists(command,
command.name == "curl" &&
command.argv.slice(1, command.argv.size()).exists(arg,
arg in ["--upload-file", "-T"]
)
)
enforce: falseEach parsed command provides:
| Field | Description |
|---|---|
name |
Lowercase executable basename with common Windows suffixes removed |
executable |
Statically parsed executable value |
argv |
Parsed arguments, including the executable at index 0 |
arguments |
Arguments with source, quoting, and expansion metadata |
assignments |
Command-scoped shell assignments |
redirects |
Input and output redirections |
dialect |
posix, powershell, or cmd |
wrappers |
Recognized launchers around the command |
statement_id |
Identifier for this command within the event |
pipeline_id |
Shared identifier for commands in one pipeline; 0 means none |
parent_statement_id |
Enclosing command identifier; 0 means none |
preview |
normal, preview, or uncertain |
function_call |
Whether this is a statically resolved shell function call |
recursive |
Whether this is a statically resolved recursive function call |
The richer collections expose these nested fields:
| Object | Fields |
|---|---|
argument |
value, source, quote, expands, subcommands |
assignment |
name, value, append |
redirect |
fd, op, target, target_source, target_quote, target_expands, subcommands |
wrapper |
name, executable, argv |
For example, detect a write or append redirect to an SSH authorization file:
shell_commands.exists(command,
command.redirects.exists(redirect,
redirect.op in ["write", "append"] &&
redirect.target.endsWith("/authorized_keys")
)
)The parser does not execute commands, expand variables, or read referenced scripts. If no safe static projection is available, rules that depend on shell_commands skip that event.
Correlate events with sequences
Use a sequence when one event is not enough to establish the behavior:
id: acme.secrets.read_then_send
version: "1.0"
enabled: true
title: Secret read followed by external transfer
description: Detects an environment file read followed by a curl command.
severity: critical
tags:
- secrets
- exfiltration
sequence:
within_events: 30
steps:
- expr: |-
event.event_type == "file.read" &&
event.file_path.matches("(^|/)\\.env$")
- expr: |-
event.event_type == "command.exec" &&
shell_commands.exists(command, command.name == "curl")
enforce: false| Sequence field | Description |
|---|---|
steps |
Two to eight ordered CEL predicates |
within |
Optional wall-clock window such as 30m |
within_events |
Optional event-count window, from the step count through 4096 |
max_matches |
Findings per rule and correlation partition; defaults to 1, maximum 16 |
Set within, within_events, or both. When both are present, both limits must hold.
Sequences correlate only within the same agent, source, session, and project. The final step is the only action an enforced sequence can block because earlier steps have already happened.
Monitor or enforce
Rules monitor by default. Set Mode → Enforce in the editor to make a matching pre-action eligible for blocking. The saved YAML contains:
enforce: true
deny_message: Contact your security team before accessing this file.severity only prioritizes the finding. It does not enable blocking.
Enforcement also depends on the coding agent exposing a supported synchronous pre-action hook. Post-action events, artifact scans, and sources without a blocking hook remain detection-only. For shell-derived matches, commands with dynamic values, substitutions, multiple statements, unsupported control flow, or parser diagnostics can be detected but are not blocked.
Use monitor mode first, review the resulting findings for false positives, and then enable enforcement for high-confidence rules.
Fix validation errors
Selecting Save runs both structural validation and CEL compilation. Common failures include:
- Defining both
exprandsequence, or neither - Using an unknown event or command field
- Returning a non-boolean CEL value
- Forgetting to escape a backslash inside a CEL string
- Using fewer than two or more than eight sequence steps
- Setting
within_eventsbelow the number of steps or above4096 - Using a duplicate rule ID
If a new catalog fails validation on an endpoint, that endpoint keeps its last known-good rules.