---
title: "Content Guardrails"
description: "Configurable content filtering, PII detection, and jailbreak protection."
order: 7
---

The guardrail engine validates inbound and outbound messages against configurable policy rules using the `github.com/initializ/guardrails` library.

## Architecture

Guardrails are implemented as a `GuardrailChecker` interface in forge-core, with the concrete engine in forge-cli wrapping the external guardrails library. Two operational modes are supported:

| Mode | Config Source | Use Case |
|------|--------------|----------|
| **File mode** (default) | `guardrails.json` in project root | Local development, standalone deployments |
| **DB mode** | MongoDB (`AgentConfig` collection) | Platform deployments with centralized config + audit |

Priority: `FORGE_GUARDRAILS_DB` env → `guardrails.json` → built-in defaults.

## Built-in Evaluators

The guardrails library provides these evaluator categories:

| Category | Direction | Description |
|----------|-----------|-------------|
| PII detection | Inbound + Outbound | Detects email, phone, SSN, credit card numbers |
| Jailbreak detection | Inbound | Detects jailbreak and prompt manipulation attempts |
| Prompt injection | Inbound | Detects injection attacks in user input |
| Command injection | Inbound | Detects shell/command injection patterns |
| Secret detection | Outbound + Tool output | Detects API keys, tokens, and private keys via regex rules |
| Custom rules | Configurable per gate | User-defined regex and keyword rules |

## Modes

| Mode | Behavior |
|------|----------|
| `enforce` | Blocks violating inbound messages; **redacts** outbound messages |
| `warn` | Logs violation, allows message to pass |

### Inbound Masking

When PII or secrets are detected in inbound messages with action `mask`, the content is redacted **before** it reaches the LLM. The LLM never sees the original sensitive data.

### Outbound Redaction

Outbound messages (from the agent to the user) are always **redacted** rather than blocked, even in `enforce` mode. Blocking would discard a potentially useful agent response over a false positive. Matched content is replaced with the library's masked output and a warning is logged.

## Configuration

### `guardrails.json`

Guardrails are configured in `guardrails.json` at the project root. This file is generated by `forge init` and can be customized:

```json
{
  "pii": {
    "enabled": true,
    "action": "mask",
    "categories": {
      "email": { "enabled": true, "action": "mask" },
      "phoneNumber": { "enabled": true, "action": "mask" },
      "ssn": { "enabled": true, "action": "mask" },
      "creditCard": { "enabled": true, "action": "mask" }
    }
  },
  "security": {
    "jailbreakDetection": {
      "enabled": true,
      "confidenceThreshold": 25,
      "action": "block"
    },
    "promptInjection": {
      "enabled": true,
      "confidenceThreshold": 30,
      "action": "block"
    },
    "commandInjection": {
      "enabled": true,
      "confidenceThreshold": 35,
      "action": "block"
    }
  },
  "customRules": {
    "rules": [
      {
        "id": "secret_openai",
        "name": "OpenAI API Key",
        "type": "regex",
        "constraint": "hard",
        "pattern": "sk-[A-Za-z0-9]{20,}",
        "action": "mask",
        "gates": ["output", "tool_call"]
      }
    ]
  },
  "gateConfig": {
    "inputGate": true,
    "toolCallGate": true,
    "outputGate": true,
    "contextGate": false,
    "streamGate": false
  }
}
```

### Custom Path

Override the guardrails config file path in `forge.yaml`:

```yaml
guardrails_path: "config/my-guardrails.json"
```

### Default Secret Patterns

The default `guardrails.json` includes regex rules for these secret types:

| Rule ID | Pattern |
|---------|---------|
| `secret_anthropic` | `sk-ant-[A-Za-z0-9\-]{20,}` |
| `secret_openai` | `sk-[A-Za-z0-9]{20,}` |
| `secret_github_pat` | `ghp_[A-Za-z0-9]{36}` |
| `secret_github_oauth` | `gho_[A-Za-z0-9]{36}` |
| `secret_github_server` | `ghs_[A-Za-z0-9]{36}` |
| `secret_github_fine` | `github_pat_[A-Za-z0-9_]{22,}` |
| `secret_aws` | `AKIA[0-9A-Z]{16}` |
| `secret_slack_bot` | `xoxb-[0-9]{10,}-[A-Za-z0-9-]+` |
| `secret_slack_user` | `xoxp-[0-9]{10,}-[A-Za-z0-9-]+` |
| `secret_private_key` | `-----BEGIN (RSA\|EC\|OPENSSH\|PRIVATE) .*KEY-----` |
| `secret_telegram` | `[0-9]{8,10}:[A-Za-z0-9_-]{35,}` |

### Gate Configuration

Gates control which evaluation points are active:

| Gate | Default | Description |
|------|---------|-------------|
| `inputGate` | `true` | Validates user messages before LLM processing |
| `toolCallGate` | `true` | Validates tool arguments before execution |
| `outputGate` | `true` | Validates agent responses before delivery |
| `contextGate` | `false` | Validates context window content |
| `streamGate` | `false` | Validates streaming chunks |

## DB Mode (Platform Deployments)

When `FORGE_GUARDRAILS_DB` is set to a MongoDB connection URI, the engine loads guardrails config from the `AgentConfig` collection and enables audit logging.

```bash
export FORGE_GUARDRAILS_DB="mongodb://localhost:27017"
export FORGE_AGENT_ID="my-agent"
export FORGE_ORG_ID="my-org"
forge run
```

The library queries `AgentConfig` with `{agent_id, org_id}` to load the `StructuredGuardrails` config. If the DB is unreachable, it falls back to file mode.

| Environment Variable | Description |
|---------------------|-------------|
| `FORGE_GUARDRAILS_DB` | MongoDB connection URI |
| `FORGE_AGENT_ID` | Agent identifier (falls back to `agent_id` in `forge.yaml`) |
| `FORGE_ORG_ID` | Organization identifier |

## Runtime

```bash
# Default: guardrails enforced (all evaluators active)
forge run

# Explicitly disable guardrail enforcement
forge run --no-guardrails
```

All configured guardrails are active by default, even without running `forge build`. Use `--no-guardrails` to opt out.

## Tool Output Scanning

The guardrail engine scans tool output via an `AfterToolExec` hook, catching secrets and PII before they enter the LLM context or outbound messages. The engine calls the library's `OutputGate` with tool metadata attached.

**Behavior by mode:**

| Mode | Behavior |
|------|----------|
| `enforce` | Returns an error identifying the violation, blocking the result from entering the LLM context |
| `warn` | Replaces matched patterns with masked content, logs a warning, allows through |

The hook writes the redacted text back to `HookContext.ToolOutput`, which the agent loop reads after all hooks fire.

## Path Containment

The `cli_execute` tool confines filesystem path arguments to the agent's working directory. This prevents social-engineering attacks where an LLM is tricked into listing or reading files outside the project.

### Shell Interpreter Denylist

Shell interpreters (`bash`, `sh`, `zsh`, `dash`, `ksh`, `csh`, `tcsh`, `fish`) are **unconditionally blocked**, even if they appear in `allowed_binaries`. Shells defeat the no-shell `exec.Command` security model by reintroducing argument interpretation and bypassing all path validation (e.g., `bash -c "ls ~/Library/Keychains"`).

### HOME Override

When `workDir` is configured, `$HOME` in the subprocess environment is overridden to `workDir`. This prevents `~` expansion inside subprocesses from reaching the real home directory. To preserve `gh` CLI authentication, `GH_CONFIG_DIR` is automatically set to the real `~/.config/gh` — but **only** when the binary being executed is `gh`. Other binaries do not receive this env var, preventing them from accessing GitHub credentials.

### Path Argument Validation

**Rules:**
- Arguments that look like paths (`/`, `~/`, `./`, `../`) are resolved and checked
- If a resolved path is inside `$HOME` but outside `workDir` → **blocked**
- System paths outside `$HOME` (e.g., `/tmp`, `/etc`) → allowed
- Non-path arguments (e.g., `get`, `pods`, `--namespace=default`) → allowed
- Flag arguments (e.g., `--kubeconfig=~/.kube/config`) → not detected as paths, allowed

Additionally, `cmd.Dir` is set to `workDir` so relative paths in subprocess execution resolve within the agent directory.

**Examples:**

| Command | Result |
|---------|--------|
| `kubectl get pods` | Allowed — no path args |
| `bash -c "ls ~/"` | Blocked — `bash` is a denied shell interpreter |
| `ls ~/Library/Keychains/` | Blocked — inside `$HOME`, outside workDir |
| `cat ../../.ssh/id_rsa` | Blocked — resolves inside `$HOME`, outside workDir |
| `jq '.' /tmp/data.json` | Allowed — system path outside `$HOME` |
| `ls ./data/` | Allowed — within workDir |

## Skill Guardrails

Skills can declare domain-specific guardrails in their `SKILL.md` frontmatter under `metadata.forge.guardrails`. These complement the global guardrails with rules authored by skill developers to enforce least-privilege and prevent capability enumeration.

### Guardrail Types

| Type | Hook Point | Direction | Behavior |
|------|-----------|-----------|----------|
| `deny_commands` | `BeforeToolExec` | Inbound | Blocks `cli_execute` commands matching a regex pattern |
| `deny_output` | `AfterToolExec` | Outbound | Blocks or redacts `cli_execute` output matching a regex pattern |
| `deny_prompts` | `BeforeLLMCall` | Inbound | Blocks user messages matching a regex (capability enumeration probes) |
| `deny_responses` | `AfterLLMCall` | Outbound | Replaces LLM responses matching a regex (binary name leaks) |

### SKILL.md Configuration

```yaml
metadata:
  forge:
    guardrails:
      deny_commands:
        - pattern: '\bget\s+secrets?\b'
          message: "Listing Kubernetes secrets is not permitted"
        - pattern: '\bauth\s+can-i\b'
          message: "Permission enumeration is not permitted"
      deny_output:
        - pattern: 'kind:\s*Secret'
          action: block
        - pattern: 'token:\s*[A-Za-z0-9+/=]{40,}'
          action: redact
      deny_prompts:
        - pattern: '\b(approved|allowed|available)\b.{0,40}\b(tools?|binaries|commands?)\b'
          message: "I help with Kubernetes cost analysis. Ask about cluster costs."
      deny_responses:
        - pattern: '\b(kubectl|jq|awk|bc|curl)\b.*\b(kubectl|jq|awk|bc|curl)\b.*\b(kubectl|jq|awk|bc|curl)\b'
          message: "I can analyze cluster costs. What would you like to know?"
```

### Pattern Details

**`deny_commands`** — Patterns match against the reconstructed command line (`binary arg1 arg2 ...`). Only fires for `cli_execute` tool calls.

**`deny_output`** — Patterns match against tool output text. The `action` field controls behavior:

| Action | Behavior |
|--------|----------|
| `block` | Returns an error, preventing the output from entering the LLM context |
| `redact` | Replaces matched text with `[BLOCKED BY POLICY]` and logs a warning |

**`deny_prompts`** — Patterns are compiled with case-insensitive matching (`(?i)`). Designed to catch capability enumeration probes like "what are the approved tools" or "list available binaries". The `message` field provides a redirect response.

**`deny_responses`** — Patterns are compiled with case-insensitive and dot-matches-newline flags (`(?is)`). Designed to catch LLM responses that enumerate internal binary names. When matched, the entire response is replaced with the `message` text.

### Aggregation

When multiple skills declare guardrails, patterns are aggregated and deduplicated across all active skills. The `SkillGuardrailEngine` runs all patterns from all skills as a single enforcement layer.

### Runtime Fallback

Skill guardrails fire both with and without `forge build`:

- **With build** — Guardrails are serialized into `policy-scaffold.json` during `forge build` and loaded at runtime
- **Without build** — The runner parses `SKILL.md` files at startup and loads guardrails directly, falling back to runtime-parsed rules when no build artifact exists

This ensures guardrails are always active during development (`forge run`) without requiring a full build cycle.

## File Protocol Blocking

The `cli_execute` tool blocks arguments containing `file://` URLs (case-insensitive). This prevents filesystem traversal attacks via tools like `curl file:///etc/passwd` that bypass path validation since `file://` URLs are not detected as filesystem paths by `looksLikePath()`.

| Input | Result |
|-------|--------|
| `curl file:///etc/passwd` | Blocked — `file://` protocol detected |
| `curl FILE:///etc/shadow` | Blocked — case-insensitive check |
| `curl http://example.com` | Allowed — only `file://` is blocked |

## Audit Events

Guardrail evaluations are logged as structured audit events:

```json
{"ts":"2026-02-28T10:00:00Z","event":"guardrail_check","correlation_id":"a1b2c3d4","fields":{"guardrail":"pii","direction":"inbound","result":"masked"}}
```

In DB mode, the guardrails library writes audit records to MongoDB automatically when `EnableAudit` is set.

See [Security Overview](overview.md) for the full security architecture.
