Skip to content

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.

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

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

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

FieldYAMLMeaning
NamenameUnique, 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.
DescriptiondescriptionFree text — a note to the next admin about the rule's intent. Never affects matching.
EnabledenabledDefault 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).

FieldYAMLMatches onNotes
Agentmatch.agentThe agent's declared nameThe self-reported identity, or the one from an agent token. OR within the list.
Usermatch.userThe human behind the source IPResolved by User-ID / directory — AD logon, CSV, or manual.
User groupsmatch.user_groupsThe user's AD/IdP groupsMatches if the user is in any listed group.
Workloadmatch.workloadThe owning process / pod / containerFrom the eBPF host sensor. Distinguishes co-located agents that share one IP.
Workload labelsmatch.workload_labelsKubernetes labels, as key=valueMatches 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:

ValueFires on
llm_promptA prompt to a model API (OpenAI, Anthropic, …).
http_requestA generic outbound HTTP call (tools, vendor APIs).
tool_callAn agent invoking a named tool.
file_readA 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).

FieldYAMLMeaning
Daystime_window.daysThe weekdays the rule is active. None selected = all 7.
Hourstime_window.hoursA HH:MM–HH:MM window (24h). If the end is before the start, it wraps past midnight (e.g. 22:00–06:00 = overnight).
Timezonetime_window.timezoneIANA 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:

DecisionYAMLEffect
AllowallowLet the request through. Still logged.
DenydenyBlock it; the agent gets an error.
RedactredactMask the matched fields, then send the sanitized request through.
Approverequire_approvalPause the request until an admin approves or denies it in the Approvals queue.
Log onlylogObserve 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_regex paths 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:

yaml
- 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 matched

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

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

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

yaml
- 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 fieldPurpose
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_ipA raw IP or CIDR (10.0.3.0/24) or list.
match.daily_cost_overFires when the agent's rolling 24h spend exceeds a dollar amount.
match.calls_per_minute_overFires above a per-minute request rate (see example 3).
match.contains_prompt_injectionRequire a prompt-injection / jailbreak pattern. Joins the content OR-group.
match.mcp_method / mcp_tool_name / mcp_resource_uriMatch Model Context Protocol (MCP) JSON-RPC messages by method, tool, or resource.
logOn 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:

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

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

Documentation for kilasec — the AI Agent Firewall.