> ## Documentation Index
> Fetch the complete documentation index at: https://anvil.thatdevguy.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Guardrails: Content Filtering, PII Redaction and Tool Policy

> Add centrally-enforced content filters, PII redaction, tool allowlists, and prompt-injection defences to every agent route in Anvil JS.

Guardrails let you enforce content policies and tool access rules at the agent level, not inside individual handlers. Pass a `guardrails` array to `defineAgent` and every request through that route is automatically screened — user input before it reaches the model, model output before it reaches the client, and tool calls before they execute. Anvil ships four built-in guardrails covering the most common needs; you can also implement the `Guardrail` interface directly for custom logic.

## The Guardrail interface

```ts theme={null}
interface Guardrail {
  name: string;
  /** Transform text (return new text) or throw GuardrailError to block. */
  onText?(text: string, ctx: TextContext): string | void;
  /** Decide whether a tool may run: allow / deny / approve. */
  onToolCall?(ctx: ToolCallContext): ToolDecision | void;
}
```

* **`onText`** receives the raw text and a `TextContext` with `role: 'input' | 'output'`. Return a (possibly modified) string to continue, or throw `GuardrailError` to block the run entirely.
* **`onToolCall`** receives the tool name, its input, and a `tainted` flag indicating whether untrusted (tool or retrieved) content is already in the conversation. Return a `ToolDecision` to explicitly allow, deny, or route the call to human approval.

### ToolDecision

```ts theme={null}
type ToolDecision = { action: 'allow' | 'deny' | 'approve'; reason?: string };
```

The most restrictive decision across all guardrails in the chain wins: `deny` beats `approve`, `approve` beats `allow`.

### GuardrailError

Throw `GuardrailError` from `onText` to immediately stop the run:

```ts theme={null}
import { GuardrailError } from 'anvil-sdk/agent';

throw new GuardrailError('my-guardrail', 'Input contains disallowed content');
```

The `guardrail` property on the error holds the name string so downstream error handlers can identify which policy fired.

## Built-in guardrails

### contentFilter

Block or redact text matching custom regular expression patterns.

```ts theme={null}
import { contentFilter } from 'anvil-sdk/agent';

contentFilter({
  deny: [/\bpassword\b/i, /\bsecret_key\b/i],
  mode: 'block',         // 'block' (default) | 'redact'
  replacement: '[REMOVED]', // used only when mode: 'redact'
  applyTo: ['input'],    // 'input' | 'output' | both (default)
})
```

<ParamField body="deny" type="RegExp[]" required>
  Patterns to screen. Each pattern is tested against the incoming text; the first match triggers the configured action.
</ParamField>

<ParamField body="mode" default="'block'" type="'block' | 'redact'">
  What to do when a pattern matches. `'block'` throws `GuardrailError`; `'redact'` replaces every match with `replacement`.
</ParamField>

<ParamField body="replacement" default="'[REDACTED]'" type="string">
  Substitution string used in `'redact'` mode.
</ParamField>

<ParamField body="applyTo" default="['input', 'output']" type="Array<'input' | 'output'>">
  Which text direction to inspect. Restrict to `['input']` to screen only user messages, or `['output']` for model responses.
</ParamField>

***

### redactPII

Automatically redact common PII patterns from input and/or output without needing to supply regexes:

```ts theme={null}
import { redactPII } from 'anvil-sdk/agent';

redactPII()
// or restrict the direction:
redactPII({ applyTo: ['input'] })
```

The following patterns are redacted:

| PII type                                  | Replacement token  |
| ----------------------------------------- | ------------------ |
| Email address                             | `[REDACTED_EMAIL]` |
| Payment card number (13–16 digits)        | `[REDACTED_CARD]`  |
| US Social Security Number (`NNN-NN-NNNN`) | `[REDACTED_SSN]`   |
| US phone number                           | `[REDACTED_PHONE]` |

<ParamField body="applyTo" default="['input', 'output']" type="Array<'input' | 'output'>">
  Which text direction to redact. Defaults to both.
</ParamField>

***

### toolPolicy

Enforce an explicit allowlist, denylist, or approval requirement on tool names:

```ts theme={null}
import { toolPolicy } from 'anvil-sdk/agent';

toolPolicy({
  allow: ['lookupOrder', 'getProductInfo'],  // only these tools may run
  deny: ['deleteAccount'],                   // always blocked
  requireApproval: ['issueRefund'],          // pauses run → HITL queue
})
```

<ParamField body="allow" type="string[]" optional>
  When set, only tools whose names appear in this list may run. Any other tool call returns a `deny` decision.
</ParamField>

<ParamField body="deny" type="string[]" optional>
  Tools that are unconditionally blocked regardless of `allow`.
</ParamField>

<ParamField body="requireApproval" type="string[]" optional>
  Tools that require human approval before executing. Matching tool calls receive an `approve` decision, suspending the run and adding it to the [HITL queue](/safety/human-in-the-loop).
</ParamField>

***

### injectionGuard

Defend against prompt-injection attacks. Once untrusted content (from a tool result or a retrieval) has entered the conversation, the model may have been instructed to make malicious tool calls. `injectionGuard` detects this "tainted" context and gates subsequent tool calls:

```ts theme={null}
import { injectionGuard } from 'anvil-sdk/agent';

injectionGuard({
  mode: 'approve',                      // 'block' | 'approve' (default) | 'allow'
  allowlist: ['getWeather', 'lookupFAQ'], // always allowed even in tainted context
})
```

<ParamField body="mode" default="'approve'" type="'block' | 'approve' | 'allow'">
  Action to take when a tool call arrives in a tainted context:

  * **`'block'`** — denies the call outright with a `deny` decision.
  * **`'approve'`** — routes the call to human approval via the HITL queue.
  * **`'allow'`** — disables the guard (useful for temporarily turning it off without removing the guardrail).
</ParamField>

<ParamField body="allowlist" type="string[]" optional>
  Tool names that are always permitted even when context is tainted. Use this for idempotent, read-only lookups that carry no injection risk.
</ParamField>

## Wiring multiple guardrails into defineAgent

```ts theme={null}
import { defineAgent } from 'anvil-sdk/agent';
import {
  contentFilter,
  redactPII,
  toolPolicy,
  injectionGuard,
} from 'anvil-sdk/agent';
import { openai } from 'anvil-sdk/llm';

export const post = defineAgent({
  client: openai({ model: 'gpt-4o' }),
  guardrails: [
    // 1. Strip PII from both input and model output
    redactPII(),

    // 2. Block input that tries to override the system prompt
    contentFilter({
      deny: [/ignore previous instructions/i, /you are now/i],
      mode: 'block',
      applyTo: ['input'],
    }),

    // 3. Restrict which tools can run and require approval for refunds
    toolPolicy({
      allow: ['lookupOrder', 'getProductInfo', 'issueRefund'],
      requireApproval: ['issueRefund'],
    }),

    // 4. Gate tool calls that arise from tainted context
    injectionGuard({
      mode: 'approve',
      allowlist: ['lookupOrder'],
    }),
  ],
  tools: [lookupOrder, getProductInfo, issueRefund],
});
```

Guardrails run in the order they appear in the array. For `onText`, each guardrail's return value is passed as input to the next — they compose as a transform pipeline. For `onToolCall`, the most restrictive decision across all guardrails wins (deny beats approve, approve beats allow).

## Implementing a custom guardrail

```ts theme={null}
import type { Guardrail } from 'anvil-sdk/agent';

const lengthGuard: Guardrail = {
  name: 'length-guard',
  onText(text, ctx) {
    if (ctx.role === 'input' && text.length > 10_000) {
      throw new GuardrailError('length-guard', 'Input exceeds 10,000 characters');
    }
  },
  onToolCall({ name }) {
    if (name === 'runArbitraryCode') {
      return { action: 'deny', reason: 'code execution not permitted' };
    }
  },
};
```

Return `void` or `undefined` from `onText` to pass the text through unchanged. Return `void` or `undefined` from `onToolCall` to abstain, letting other guardrails in the chain decide.
