# How OmniRoute's Guardrails System Protects AI Requests: PII Masking and Injection Defense

> Learn how OmniRoute's guardrails protect AI requests with PII masking and prompt injection defense. Discover configurable hooks for enhanced security in the request pipeline.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: internals
- Published: 2026-07-10

---

**OmniRoute's guardrails system runs configurable pre-call and post-call hooks to mask PII and detect prompt injection attacks before they reach downstream providers.**

The guardrails framework in the [diegosouzapw/OmniRoute](https://github.com/diegosouzapw/OmniRoute) repository provides a modular security layer that intercepts every inbound chat request. It combines **PII redaction** with **prompt injection detection** to sanitize payloads and block malicious inputs via a registry-based pipeline.

## How the OmniRoute Guardrails Pipeline Works

The system processes requests through a strict sequence defined in [`src/lib/guardrails/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/guardrails/registry.ts). Every HTTP request traverses the following path:

1. **Resolution** –_disabled guardrails are identified from headers, metadata, or API key settings.
2. **Pre-call execution** – registered guardrails execute `preCall` methods to inspect and modify the payload.
3. **Downstream execution** – the sanitized request reaches LLM providers (OpenAI, Anthropic, etc.).
4. **Post-call execution** – guardrails execute `postCall` methods to redact sensitive data from responses.

Guardrails execute in priority order: **PII Masker (10)** runs before **Prompt Injection (20)**, ensuring personal data is stripped before injection analysis occurs.

## Resolving Disabled Guardrails

Before running the pipeline, `resolveDisabledGuardrails()` aggregates configuration from three sources:

- **HTTP headers**: `x-omniroute-disabled-guardrails` (or legacy `x-disabled-guardrails`)
- **Request body**: `metadata.disabledGuardrails` array
- **API key metadata**: database-stored `disabledGuardrails` column

```typescript
// src/lib/guardrails/registry.ts
export function resolveDisabledGuardrails({
  apiKeyInfo,
  body,
  headers,
}: {
  apiKeyInfo?: Record<string, unknown> | null;
  body?: unknown;
  headers?: HeadersLike;
}): string[] {
  const headerDisabled =
    getHeaderValue(headers, "x-omniroute-disabled-guardrails") ||
    getHeaderValue(headers, "x-disabled-guardrails");

  return [...coerceDisabledGuardrails(apiKeyDisabled)]
    .concat(coerceDisabledGuardrails(bodyRecord?.disabledGuardrails))
    .concat(coerceDisabledGuardrails(metadata?.disabledGuardrails))
    .concat(coerceDisabledGuardrails(headerDisabled))
    .filter((value, index, list) => list.indexOf(value) === index);
}

```

The resulting array (e.g., `["pii-masker", "prompt-injection"]`) is passed to the registry via the `GuardrailContext`.

## Pre-Call Guardrail Execution

The core chat handler in [`src/sse/handlers/chat.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/sse/handlers/chat.ts) invokes the registry at approximately line 415:

```typescript
const preCallGuardrails = await guardrailRegistry.runPreCallHooks(body, {
  log: requestLogger,
  disabledGuardrails,
});

if (preCallGuardrails.blocked) {
  return NextResponse.json({ error: preCallGuardrails.message }, { status: 400 });
}

body = preCallGuardrails.payload; // mutated by guardrails

```

`runPreCallHooks` iterates over registered guardrails, skips disabled ones, and executes their `preCall` methods. Each execution can:

- **Block** the request (`block: true`) and return an error message
- **Modify** the payload (`modifiedPayload`) for sanitization
- **Pass silently** with metadata logging only

The registry automatically logs each result:

```typescript
logger.debug?.(
  "GUARDRAIL",
  `${guardrail.name} pre-call ${execution.blocked ? "blocked" : modified ? "modified" : "passed"}`
);

```

## Prompt Injection Protection

The `PromptInjectionGuardrail` class in [`src/lib/guardrails/promptInjection.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/guardrails/promptInjection.ts) implements multi-layered attack detection.

### Configuration and Modes

Behavior is controlled via environment variables and runtime options:

| Source | Variable | Effect |
|--------|----------|--------|
| Environment | `INPUT_SANITIZER_MODE` | `block`, `warn`, or `log` (default: `warn`) |
| Environment | `INPUT_SANITIZER_ENABLED` | Set to `"false"` to disable entirely |
| Feature Flag | `INJECTION_GUARD_MODE` | Database-backed override taking precedence |
| Runtime | `blockThreshold` | Severity threshold: `low`, `medium`, or `high` |

```typescript
// src/lib/guardrails/promptInjection.ts
export interface PromptInjectionGuardrailOptions {
  blockThreshold?: "low" | "medium" | "high";
  customPatterns?: PatternLike[];
  enabled?: boolean;
  mode?: "block" | "warn" | "log";
  priority?: number;
}

```

### Detection Engine and Pattern Matching

The `evaluatePromptInjection` function performs a bounded scan of the first **16 KB** (`MAX_INJECTION_SCAN_BYTES`) for performance:

1. Calls `sanitizeRequest` for built-in injection and PII detection
2. Flattens message contents via `extractMessageContents`
3. Runs regex patterns against `detectWithPatterns`
4. Combines and deduplicates results

```typescript
function detectWithPatterns(text: string, patterns: ReturnType<typeof normalizePatternEntry>[]) {
  const detections: Detection[] = [];
  for (const rule of patterns) {
    const match = text.match(rule.pattern);
    if (match) {
      detections.push({
        pattern: rule.name,
        severity: rule.severity,
        match: match[0].slice(0, 50)
      });
    }
  }
  return detections;
}

```

### Blocking Logic and Thresholds

The `shouldBlock` function evaluates severity scores:

```typescript
function shouldBlock(detections: Detection[], threshold: "low" | "medium" | "high") {
  const minimumSeverity = SEVERITY_SCORES[threshold] || SEVERITY_SCORES.high;
  return detections.some(d => (SEVERITY_SCORES[d.severity] || 0) >= minimumSeverity);
}

```

When `mode` is `"block"` and the threshold is exceeded, the guardrail returns:

```typescript
{
  block: true,
  message: "Request rejected: suspicious content detected",
  meta: { detections: decision.result.detections.length }
}

```

Otherwise, it passes with metadata indicating flagged content.

## PII Masking in the Request Pipeline

The `PIIMaskerGuardrail` in [`src/lib/guardrails/piiMasker.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/guardrails/piiMasker.ts) operates during both pre-call and post-call phases, activated when `PII_REDACTION_ENABLED` equals `"true"` and `INPUT_SANITIZER_MODE` equals `"redact"`.

### Pre-Call Redaction

The guardrail deep-clones payloads and traverses string fields to redact detected personal information:

```typescript
// src/lib/guardrails/piiMasker.ts
function cloneAndMaskRequestPayload(payload: unknown) {
  const clonedPayload: JsonRecord = JSON.parse(JSON.stringify(payload));
  
  if (typeof clonedPayload.system === "string") {
    // redact system prompt
  }
  
  if (Array.isArray(clonedPayload.messages)) {
    // redact user and assistant messages
  }
  
  return { detections, modified, payload: modified ? clonedPayload : payload };
}

```

### Post-Call Redaction

After the provider responds, the `postCall` method sanitizes outputs via `sanitizePIIResponse` and `maskResponsesOutput`:

```typescript
async postCall(response: unknown, _context: GuardrailContext): Promise<GuardrailResult<unknown>> {
  const clonedResponse = JSON.parse(JSON.stringify(response)) as JsonRecord;
  const sanitized = sanitizePIIResponse(clonedResponse) as JsonRecord;
  const modifiedResponsesShape = maskResponsesOutput(sanitized);
  
  return { 
    block: false, 
    meta: { redacted: true }, 
    modifiedResponse: sanitized 
  };
}

```

If no PII is detected, the guardrail returns `{ block: false }` without modification.

## Integration in the Core Chat Handler

The complete flow in [`src/sse/handlers/chat.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/sse/handlers/chat.ts) demonstrates the guardrails system integration:

```typescript
import { guardrailRegistry, resolveDisabledGuardrails } from "@/lib/guardrails";

export async function handleChat(request) {
  const { body, headers } = request;
  const disabledGuardrails = resolveDisabledGuardrails({ apiKeyInfo, body, headers });

  // Pre-call guardrails (PII stripping + injection detection)
  const pre = await guardrailRegistry.runPreCallHooks(body, { 
    log: logger, 
    disabledGuardrails 
  });
  
  if (pre.blocked) return errorResponse(pre.message);
  
  // Execute against LLM provider
  const providerResponse = await executeProvider(pre.payload);
  
  // Post-call guardrails (response PII redaction)
  const post = await guardrailRegistry.runPostCallHooks(providerResponse, { 
    log: logger, 
    disabledGuardrails 
  });
  
  return formatResponse(post.response);
}

```

This architecture ensures that **no raw request reaches the provider without inspection**, and **no response reaches the client without sanitization**.

## Configuration and Customization

### Disabling Guardrails via Headers and Metadata

To bypass specific guardrails for a single request:

```http
POST /v1/chat/completions
x-omniroute-disabled-guardrails: pii-masker,prompt-injection

```

Alternatively, via request metadata:

```json
{
  "metadata": { 
    "disabledGuardrails": ["prompt-injection"] 
  },
  "messages": []
}

```

### Environment Variables and Feature Flags

Enable aggressive blocking and PII redaction globally:

```bash

# Block on high-severity injections

export INPUT_SANITIZER_MODE=block

# Enable PII redaction

export PII_REDACTION_ENABLED=true
export INPUT_SANITIZER_MODE=redact

# Optional: Override via database feature flag

export INJECTION_GUARD_MODE=block

```

For custom implementations, register guardrails with specific priorities:

```typescript
guardrailRegistry.register(
  new PromptInjectionGuardrail({
    blockThreshold: "medium",
    customPatterns: [/secret\s+token/i, "DROP TABLE"],
    mode: "block",
    priority: 5,
  })
);

```

## Summary

- **Modular execution**: The `GuardrailRegistry` in [`src/lib/guardrails/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/guardrails/registry.ts) orchestrates pre-call and post-call hooks with configurable priority levels.
- **PII protection**: Deep-copy redaction runs before injection detection (priority 10 vs 20) to prevent personal data from interfering with security patterns.
- **Injection detection**: Bounded 16 KB scans with customizable regex patterns and severity thresholds (`low`/`medium`/`high`).
- **Flexible disabling**: Header, body metadata, or API key settings can disable guardrails per-request without code changes.
- **Fail-closed option**: Set `INPUT_SANITIZER_MODE=block` to reject suspicious requests rather than merely logging them.

## Frequently Asked Questions

### How do I disable the guardrails system for specific API requests?

Send the `x-omniroute-disabled-guardrails` HTTP header with a comma-separated list of guardrail names (e.g., `pii-masker,prompt-injection`), or include `"disabledGuardrails": ["prompt-injection"]` in the request metadata body. The system also respects legacy header names for backward compatibility.

### What happens when the prompt injection guardrail detects suspicious content?

Behavior depends on the configured `mode`. In `block` mode (set via `INPUT_SANITIZER_MODE` or `INJECTION_GUARD_MODE`), the request returns a 400 error with the message "Request rejected: suspicious content detected" before reaching the LLM provider. In `warn` mode (default), the request proceeds but logs detection metadata. In `log` mode, detections are recorded silently without warnings.

### Why does PII masking run before prompt injection detection?

The guardrails execute in priority order: PII Masker uses priority 10 while Prompt Injection uses priority 20. This ensures that sensitive personal information is redacted from the payload before the injection scanner analyzes the text, preventing false positives where PII patterns might resemble injection syntax and ensuring user privacy during security analysis.

### Can I add custom injection patterns beyond the built-in detection rules?

Yes. When registering the `PromptInjectionGuardrail` class, pass a `customPatterns` array containing regex patterns or string literals. These patterns are evaluated alongside default rules in `detectWithPatterns()`, and you can assign custom severity levels to each pattern for threshold-based blocking.