Policy rule reference — every field
This is the exhaustive reference for the rule editor: every field on every tab, what it does, and — the part that trips people up — how fields combine when you fill in more than one. For a gentler intro see Writing policy rules; for the model behind it see Policy semantics.
The three rules that govern everything
Before the fields, internalize these. They explain every behavior below.
First match wins. A policy is an ordered list. The engine walks it top-to-bottom and applies the first rule that matches; nothing below is considered. If no rule matches, the policy's default action applies (the pinned rule at the bottom of the list). This is iptables / ACL semantics — so order deny/redact rules above broad allow rules.
AND across fields, OR within a field. Everything you fill in on a rule must all be true for the rule to match (logical AND). But a single field that holds a list matches if any value hits (logical OR). Example: Agent =
[bot-a, bot-b], Host =api.openai.com, matches when (agent is bot-a or bot-b) and host is api.openai.com.A blank field means "any." Every empty field is a wildcard. A rule with nothing filled in matches every request. You narrow a rule by adding conditions — each one you add makes the rule match less.
The mental model
Each field you add is another AND — it makes the rule stricter, matching fewer requests. Each value you add within a field is another OR — it makes that one condition broader. Build a rule by asking "what must all be true?" (separate fields) and "what are the acceptable values for each?" (chips within a field).
Tab: General
Identity and housekeeping for the rule itself.
| Field | YAML | Meaning |
|---|---|---|
| Name | name | Unique, human-readable. Shown in Live Traffic and audit rows as matched_rule, so name it for why it exists (redact_pii_egress), not what it is. |
| Description | description | Free text — a note to the next admin about the rule's intent. Never affects matching. |
| Enabled | enabled | Default true. A disabled rule is skipped entirely (as if deleted) but kept in the list — the clean way to park a rule without losing it. Toggled from the rules list, or set in YAML. |
Name and Description are metadata; they change nothing about which requests match.
Tab: Source — who is making the request
All Source fields are AND-ed together, and each is a list that's OR-ed within. Leave all blank to match any source (the panel says so).
| Field | YAML | Matches on | Notes |
|---|---|---|---|
| Agent | match.agent | The agent's declared name | The self-reported identity, or the one from an agent token. OR within the list. |
| User | match.user | The human behind the source IP | Resolved by User-ID / directory — AD logon, CSV, or manual. |
| User groups | match.user_groups | The user's AD/IdP groups | Matches if the user is in any listed group. |
| Workload | match.workload | The owning process / pod / container | From the eBPF host sensor. Distinguishes co-located agents that share one IP. |
| Workload labels | match.workload_labels | Kubernetes labels, as key=value | Matches if any listed label is on the pod (e.g. team=payments). |
Identity fields fail closed
If you set User, User groups, Workload, or Workload labels but the collector can't resolve an identity for the request's source (no directory data, no workload attribution), the rule does not match — it falls through to the next rule. This is deliberate: an identity-scoped rule must never match a request whose identity is unknown. So a user_groups: [finance] → deny rule won't accidentally block an unattributed request, but it also won't protect against one — put a broader catch rule below it if you need belt-and-suspenders. See the identity model.
Combining example. Workload = payments-bot and User groups = finance means the payments-bot process and running as a finance user — both must resolve and both must hold. That's usually stricter than intended; most rules use one source dimension.
Tab: Destination — what is being called, and what's in it
Again: each field AND-ed, lists OR-ed within, blank = any.
Request type
YAML match.action. The verb of the request — leave as (any request type) unless you specifically mean one:
| Value | Fires on |
|---|---|
llm_prompt | A prompt to a model API (OpenAI, Anthropic, …). |
http_request | A generic outbound HTTP call (tools, vendor APIs). |
tool_call | An agent invoking a named tool. |
file_read | A file-read action (framework-dependent). |
Hosts
YAML match.destination.host. The destination hostname. Wildcards work (*.openai.com matches every subdomain; api.openai.com is exact). The Quick add buttons (OpenAI, Anthropic, Copilot, …) drop in that provider's known host set so you don't have to remember them. Applies to both http_request and llm_prompt traffic. Multiple hosts = OR.
Tools
YAML match.destination.tool. For tool_call requests — the tool name (shell.exec, sql_execute). Only meaningful when the request type is (or includes) tool_call.
Models
YAML match.destination.model. The model identifier (claude-sonnet-4-6, gpt-4o). Scope a rule to specific — usually expensive or frontier — models.
Content matchers
YAML match.contains_pii, match.contains_secret. Toggle PII and/or Secrets / API keys to require that the request body contains that class of sensitive data. The built-in scanners look for SSNs, card numbers, emails, phone numbers (PII) and API keys, AWS keys, bearer tokens, private keys (secrets).
Content matchers are an OR-group
If you enable both PII and Secrets, the rule matches a request that contains PII or a secret — not one that must contain both at once. (This matches how you'd read it aloud: "redact if it leaks PII or a secret.") The content group as a whole is still AND-ed with the structural fields above — so a rule with Host = api.openai.com and PII+Secrets means going to OpenAI and (contains PII or a secret).
Tab: Schedule — when the rule is active
YAML match.time_window. Leave everything blank to make the rule always-on. Filling any part restricts the rule to that window; outside it, the rule simply doesn't match (and the engine moves on).
| Field | YAML | Meaning |
|---|---|---|
| Days | time_window.days | The weekdays the rule is active. None selected = all 7. |
| Hours | time_window.hours | A HH:MM–HH:MM window (24h). If the end is before the start, it wraps past midnight (e.g. 22:00–06:00 = overnight). |
| Timezone | time_window.timezone | IANA zone the hours are evaluated in. Default UTC — set this to your business timezone or the hours will be off. |
Combining example. Days = Mon–Fri, Hours = 09:00–17:00, TZ = America/Los_Angeles → the rule is active only during Pacific business hours. A request at 8pm or on Saturday won't match this rule and falls through.
Tab: Action — what to do when it matches
YAML action. Exactly one decision per rule:
| Decision | YAML | Effect |
|---|---|---|
| Allow | allow | Let the request through. Still logged. |
| Deny | deny | Block it; the agent gets an error. |
| Redact | redact | Mask the matched fields, then send the sanitized request through. |
| Approve | require_approval | Pause the request until an admin approves or denies it in the Approvals queue. |
| Log only | log | Observe only — record it, enforce nothing. The way to test a rule's blast radius before switching it to deny/redact. |
Reason
YAML reason. Free text surfaced in the decision and audit log (PII detected in outbound payload — masking before send). Write it for whoever reads the log later.
Content shortcuts
The PII / Secrets toggles here are a convenience copy of the Destination tab's Content matchers — the exact same contains_pii / contains_secret match conditions, repeated on the Action tab so you can flip one on without tab-hopping when you started here. Toggling PII on the Action tab and on the Destination tab does the same thing. They decide when the rule matches, not what gets masked.
What gets redacted (redact_fields)
For a Redact action, which fields are masked is controlled by redact_fields in the YAML:
- Leave it empty (the default) and Kilasec masks whatever triggered the match — the PII / secret /
arg_regexpaths that caused the rule to fire. This is the right default for a "redact PII" rule and needs no configuration. - To mask specific fields regardless, list explicit dotted paths in YAML (
body.ssn,messages.0.content).
Worked examples — combining conditions
The whole point of the tabs is stacking conditions. Read each YAML as "match when all of these hold."
1 — Redact PII or secrets to any AI provider. One content condition, one action:
- name: redact_sensitive_egress
match:
contains_pii: true # PII …
contains_secret: true # …OR a secret (content OR-group)
action: redact
reason: "Sensitive data in outbound prompt — masking"
# redact_fields omitted → mask whatever matchedMatches any request whose body has PII or a secret, anywhere. Nothing else is constrained, so it's broad — put it near the top.
2 — Block a team's agents from a risky tool, off-hours only. Source AND destination AND schedule:
- name: no_shell_exec_after_hours
match:
user_groups: [contractors] # who
destination: { tool: shell.exec } # what
action: tool_call
time_window: # when
hours: "18:00-08:00" # overnight (wraps midnight)
timezone: America/New_York
action: deny
reason: "shell.exec restricted for contractors outside business hours"All four must hold: a contractor and calling shell.exec and it's a tool_call and the time is in the overnight window. A daytime call, or one from a non-contractor, falls through.
3 — Rate-limit a runaway agent. A field that only lives in YAML today:
- name: rate_limit_eval_bot
match:
agent: eval-bot
calls_per_minute_over: 60 # fires when >60 calls/min in the last 60s
action: require_approval
reason: "eval-bot exceeded 60 req/min — holding for a human"4 — Require approval for a workload hitting a frontier model. Source (workload) AND destination (model):
- name: approve_frontier_for_payments
match:
workload: payments-bot
destination: { model: claude-opus-4-6 }
action: require_approval
reason: "payments-bot → Opus needs sign-off"Fields that live only in YAML (advanced)
The editor covers the common cases; the engine understands more. Edit the policy YAML directly (Editor → raw) to use these:
| YAML field | Purpose |
|---|---|
match.arg_regex | {dotted.path: regex} — match when a specific field matches a pattern. AND-ed separately (not part of the content OR-group). |
match.source_ip | A raw IP or CIDR (10.0.3.0/24) or list. |
match.daily_cost_over | Fires when the agent's rolling 24h spend exceeds a dollar amount. |
match.calls_per_minute_over | Fires above a per-minute request rate (see example 3). |
match.contains_prompt_injection | Require a prompt-injection / jailbreak pattern. Joins the content OR-group. |
match.mcp_method / mcp_tool_name / mcp_resource_uri | Match Model Context Protocol (MCP) JSON-RPC messages by method, tool, or resource. |
log | On an allow rule, set log: true to still emit an audit entry. |
Ordering — the gotcha
Because first-match-wins, a broad allow placed above a specific deny makes the deny dead:
- name: allow_all_openai # ← matches everything to OpenAI first…
match: { destination: { host: "*.openai.com" } }
action: allow
- name: block_openai_secrets # ← …so this never runs. Move it ABOVE.
match:
destination: { host: "*.openai.com" }
contains_secret: true
action: denyRule of thumb: most specific and most restrictive at the top, broad allows at the bottom, default action last. Use Log only to safely trial a new rule's reach before promoting it to deny or redact.