> For clean Markdown of this page, append .md to its URL. For the complete documentation index, see https://www.superagent.sh/llms.txt.


Create, validate, assign, monitor, and enforce Runtime Guardrails rules.

# Write and enforce rules

## Create a rule

Rules detect behavior in normalized coding-agent activity. They are evaluated locally on each client and distributed through client groups.

### Default rules

The Rules table includes the full built-in catalog alongside custom rules. Default rows are labeled **Default** and apply to all clients.

Deleting a default rule creates a disabled organization override; it does not modify the client binary. The row remains visible with a **Disabled** status so administrators can restore it later. Select the restore action to remove the override and reactivate the embedded default.

Custom rules are labeled **Custom** and can be assigned to selected groups. Deleting a custom rule permanently removes it.

### Generate a rule with the assistant

1. Open **Agents → Rules**.
2. Select **New rule**.
3. Describe the behavior you want to detect in the **rule generator** terminal.
4. Select **Generate YAML**.
5. Review the generated YAML in the editor.
6. Ask follow-up questions to refine it, or edit the YAML directly.
7. Assign one or more client groups and select **Validate and save**.

For example:

```text
Alert when an agent reads a .env file and then sends data to an external URL.
Make this high severity, but do not block it.
```

After the first response, the YAML editor appears above the terminal. Follow-up terminal instructions use the current YAML as context:

```text
Limit this to events in production projects and change the window to 30 events.
```

The assistant is available only to organization owners. Generation is rate-limited and requires AI Gateway configuration on self-hosted deployments.

### Write YAML manually

Open **New rule** and replace the commented example in the YAML editor. You do not need to send a prompt to the rule assistant.

| Field | Required | Description |
| --- | --- | --- |
| `id` | Yes | Stable, dot-separated identifier using lowercase letters, numbers, dashes, or underscores |
| `version` | Yes | Quoted version copied into alerts |
| `enabled` | No | Whether the rule is active; defaults to `true` |
| `title` | Yes | Human-readable alert title |
| `description` | No | Precise explanation of what matches and what the finding proves |
| `severity` | Yes | `info`, `low`, `medium`, `high`, or `critical` |
| `tags` | No | Categories copied into findings, including relevant MITRE ATT&CK tags |
| `expr` | One of | CEL predicate evaluated against one event |
| `sequence` | One of | Ordered multi-event detection |
| `enforce` | No | Blocks matching actions when true; defaults to `false` (monitor only) |
| `deny_message` | No | Message returned when an enforceable action is denied |

Define exactly one of `expr` or `sequence`.

See the [rule language
reference](https://www.superagent.sh/docs/security-workers/agent-guardrails/rules#rule-language-reference)
for every normalized event field, CEL operator, parsed shell command, sequence
window, and validation rule.

### Single-event rules

Use `expr` when one event is enough to identify the behavior:

```yaml
id: acme.secrets.env_read
version: "1.0"
enabled: true
title: Environment file read
description: Detects reads of environment files that commonly contain credentials.
severity: high
tags:
  - secrets
  - attack.t1552.001
expr: |-
  event.event_type == "file.read" &&
  event.file_path.matches("(^|/)\\.env$")
```

Useful fields include:

- `event.event_type`
- `event.file_path`
- `event.command`
- `event.tool_name`
- `event.url`
- `event.content_preview`
- `event.project_path`
- `event.source_agent`
- `event.tags`

Expressions support normal CEL operators and helpers such as `==`, `!=`, `&&`, `||`, `contains()`, `startsWith()`, `endsWith()`, and `matches()`.

### Sequence rules

Use `sequence` when events must occur in order:

```yaml
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 an outbound network indicator.
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 == "network.indicator" &&
        event.url != ""
```

A sequence requires two to eight steps and at least one window:

- `within` — wall-clock duration such as `30m`
- `within_events` — event-count window, up to 4096

### Monitoring and enforcement

Rules are monitor-only unless the YAML contains `enforce: true` (or Mode is set to Enforce in the UI). Installed hooks can deny matching actions for enforce rules; monitor rules only record findings.

Four high-confidence catastrophic-action defaults start in Enforce mode: recursive deletion of root or home, destructive disk operations, fork bombs, and forced termination of all accessible processes. Organization owners can change any of them to Monitor or Disabled. Other default rules start in Monitor mode to avoid blocking legitimate developer and administrator workflows.

```yaml
enforce: true
deny_message: Contact your security team before accessing this file.
```

Severity does not enable blocking. It only prioritizes the resulting alert.

### Assign rules to groups

Use terminal commands to manage group assignments while editing:

```text
/groups
/group add Production
/group remove Production
/group create New group
```

A client receives the union of rules assigned to all of its groups. Rule IDs are unique within an organization, so assigning one rule through multiple groups does not duplicate it.

Use `/save` to validate and save, `/clear` to reset the YAML, `/close` to close the panel, and `/help` to list commands.

### Validation

Superagent validates the YAML structure and compiles every generated or edited rule with the same Numbat monitoring engine before accepting it. Compiler diagnostics are shown in the rule editor so unsupported fields, functions, and expressions can be fixed before saving.

Each endpoint validates downloaded rules again before activation. If a new catalog fails validation, the endpoint keeps its last known-good rules.

On self-hosted Node deployments, you can set `AGENT_RULE_VALIDATOR_PATH` to an executable Numbat v0.1.2 binary. When it is unset, the server downloads the pinned archive for macOS or Linux on arm64 or x64 during the first validation in each process, verifies the archive SHA-256 checksum, and keeps the validator only in that process's private temporary directory. Rule saves fail safely if the validator is unavailable.

## Rule language reference

You can write Runtime Guardrails rules directly in the YAML editor. The rule assistant is optional.

1. Open **Agents → Rules**.
2. Select **New rule**.
3. Replace the commented example in the editor with your rule.
4. Choose the client groups that should receive it and set the rule to Monitor, Enforce, or Disabled.
5. 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`:

```yaml
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: false
```

Use 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.

```cel
event.event_type == "file.write" &&
event.file_path.startsWith("/etc/")
```

Common CEL operations include:

| Operation | Example |
| --- | --- |
| Boolean logic | `a && b`, `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:

- `event` is the normalized action and context recorded across supported coding agents.
- `shell_commands` is a parsed, rule-only view of commands found in `event.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:

```text
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.timestamp
```

All 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:

```cel
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:

```yaml
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: false
```

Each 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:

```cel
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:

```yaml
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:

```yaml
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 `expr` and `sequence`, 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_events` below the number of steps or above `4096`
- Using a duplicate rule ID

If a new catalog fails validation on an endpoint, that endpoint keeps its last known-good rules.

## Next steps

- [Deploy rules with Runtime Guardrails](https://www.superagent.sh/docs/security-workers/agent-guardrails/runtime)
- [Manage rules with the Agents API](https://www.superagent.sh/docs/api/agents)
- [Review findings and reports](https://www.superagent.sh/docs/concepts/findings-and-reports)

---
Source: https://www.superagent.sh/docs/security-workers/agent-guardrails/rules
Index: https://www.superagent.sh/llms.txt
