Skip to main content
Agentic Runtime Security policies are authored in the HiddenLayer Console using CEL detection rules. This page walks through the policy model and how to create rules, build a policy, and apply it.
The policy experience lives in the Console at Runtime Security > Policy and has three tabs: Policy, Rules, and Configuration.

How policy works

The pieces build on each other:
  • A detection rule is a reusable, named CEL expression (with a threat level) that flags something in a session, for example prompt injection in user input.
  • A policy composes one or more detection rules and assigns each an action: Block, Detect, or Redact.
  • A policy is associated with a project, and that project’s policy is applied at evaluation time when you send the project header.
Detection and enforcement are separate: a rule only describes a condition, and the policy decides what to do about it. Because the action lives on the policy rather than the rule, one rule can be reused by several policies, for example detect-only in staging and blocking in production, with no change to the rule itself. So the workflow is: write the rules you care about, compose them into a policy with actions, attach the policy to a project, then reference that project at evaluation time. The rest of this page walks each step.

Author a detection rule

Start by writing the individual rules. A rule is just a named CEL expression you can reuse across policies.
  1. In the Console, go to Runtime Security > Policy and open the Rules tab.
  2. Click Create Rule.
  3. Fill in:
    • Name
    • Description
    • Threat Level: Critical, High, Medium, or Low.
    • Expression: the CEL expression (see below).
  4. Click Create.
The built-in System Rules are viewable on the Rules tab as starting references.

Choosing the message scope

A rule evaluates over the messages in a session, and the most important choice is which messages it looks at. For real-time detect/block on the current turn, scope the rule to the latest message so it fires once, on the turn that produced the condition:
Be mindful with messages.exists(m, ...), which matches any message in the session. Because a matched message stays in the conversation history, a messages.exists(...) rule used for enforcement fires on the original turn and every turn after it. Scope to the latest message instead; a scope mismatch like this is the most common cause of a rule that misbehaves. Guard indexed access with size(messages) > 0 so a rule is safe on an empty conversation.

Writing CEL expressions

Detection-rule expressions evaluate over the session. The most common pattern targets the latest message, messages[size(messages) - 1], reading m.analysis.signals.<signal>.<field> and optionally filtering on m.role (user, assistant, system, or tool). The signal fields available on each message: The PII entity vocabulary is configurable. The default entity pack includes EMAIL_ADDRESS, PHONE_NUMBER, CREDIT_CARD, US_SSN, IBAN_CODE, IP_ADDRESS, US_BANK_NUMBER, US_DRIVER_LICENSE, US_ITIN, US_PASSPORT, UK_NINO, URL, and GENERIC_API_KEY. code.languages is drawn from a fixed set: bash, c, cpp, css, go, java, javascript, php, python, ruby, rust, sql, and typescript. A single message can match more than one.

Common policy examples

Once you understand the policy model and message scope, these examples show common rules and actions you can adapt. Each example includes the CEL expression when a rule is required, and the policy action to assign to that rule.

Block prompt injection in the latest input

Detect prompt injection on the current turn; set the rule’s action to Block.

Redact PII in outputs

Redaction is configured on the policy, not as a CEL rule. Choose the PII entity types to redact (for example US_SSN, EMAIL_ADDRESS, PHONE_NUMBER) and the redaction strategy in the policy configuration. Use a CEL rule only when you want to detect or block PII rather than redact it.

Detect or block PII

To act on PII instead of redacting it, write a rule that fires on a specific entity (here, a Social Security number in an output); set its action to Detect or Block.

Detect suspicious URLs

Flag a URL pointing at a known exfiltration host; set the rule’s action to Detect. Match hosts case-insensitively with matches("(?i)…") so a rule can’t be bypassed with different casing (for example WEBHOOK.SITE).
The PII detector also reports URL as an entity, which you can use for a simple presence check:

Block oversized inputs

Stop abusively large inputs on the current turn; set the rule’s action to Block.

Monitor tool use

Watch for a specific tool call; set the rule’s action to Detect. Match on the content-part type rather than the message role, so the rule works across providers.

More examples

Detect code in the latest input:
Or target a specific language:
Flag non-English input on the current turn:

CEL reference

Detection rules use standard CEL plus a set of extensions. The following is what rule authors can rely on.
  • Root bindings: metadata, messages, and tools (plus the reserved expr namespace for cross-referencing other rules).
  • Macros: exists, all, exists_one, filter, and map (receiver-style, including two-variable forms), plus the global has() to safely check optional fields.
  • Operators: ==, !=, &&, ||, !, in, the ternary ? :, arithmetic, comparisons, and [...] indexing.
  • Functions: strings (contains, matches, startsWith, endsWith, size, and more), list/set operations, math, base64, and IP/CIDR helpers such as cidr("10.0.0.0/8").containsIP(...).
  • Regex: author-facing regular expressions are available via .matches("...").
For the exhaustive standard library, see the CEL documentation.

Common CEL patterns

A few idioms recur across rule expressions:
  • Guard before indexing (safe on empty conversations): size(messages) > 0 && messages[size(messages) - 1]...
  • Restrict by role: messages.exists(m, m.role in ["user", "system"] && ...)
  • Combine signals with boolean operators: ... .url.urls.size() > 0 && ... .personally_identifiable_information.entities.size() > 0
  • Set membership with in: "US_SSN" in m.analysis.signals.personally_identifiable_information.entities
  • Content-part traversal: m.content.exists(c, c.type == "text" && c.text.contains("password"))

Build the policy

With your rules created, compose them into a policy and decide what each rule does.
  1. In the Console, go to Runtime Security > Policy and open the Policy tab.
  2. Create a policy and attach your rules, setting each to Block or Detect.
  3. Configure Redact entities and strategy as needed.
Redact is a policy action, not a rule: you choose which PII entity types to redact on the policy, and no rule expression is required. The PII detector runs on every message.

Apply the policy

A policy takes effect once a project references it. A project is the unit you attach a policy to and reference at evaluation time. Associate the policy with a project, then send that project’s id with the HL-Project-Id header (the hl_project_id keyword in the SDK) on each evaluation call, and that project’s policy is applied.
TipYou can prototype and validate CEL in Runtime Security > Search, save it as a query, and import it when creating a rule.

Next

Integrations

Put Agentic Runtime Security in front of your AI traffic with a guardrail or gateway integration.