# How OmniRoute Implements Guardrails: PII Masking and Prompt Injection Detection Explained

> Discover how OmniRoute's guardrails protect your LLM by masking PII and detecting prompt injection with pre and post-call hooks. Secure your AI applications today.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: deep-dive
- Published: 2026-08-03

---

**OmniRoute's guardrails framework protects every incoming request by running pre-call and post-call hooks that mask personally identifiable information (PII) and detect prompt injection attacks before they reach downstream LLM providers.**

The guardrails system in **OmniRoute** is a TypeScript-based security layer that sits between HTTP clients and AI model providers like OpenAI and Anthropic. It operates through a centralized registry pattern, executing configurable pipelines that can modify, block, or log requests based on security policies. This article breaks down exactly how PII masking and prompt injection detection work in the OmniRoute codebase, with direct references to the implementation files and configuration options.

## Guardrails Architecture: Pre-Call and Post-Call Hooks

OmniRoute processes every chat completion request through a two-stage guardrail pipeline orchestrated by `GuardrailRegistry`. The registry loads guardrail implementations, resolves which are disabled, and executes them in priority order.

The execution flow in [`src/sse/handlers/chat.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/sse/handlers/chat.ts) (~line 415) shows the integration:

```typescript
// src/sse/handlers/chat.ts
const preCallGuardrails = await guardrailRegistry.runPreCallHooks(body, {
  log: requestLogger,
  disabledGuardrails,
});
if (preCallGuardrails.blocked) {
  return NextResponse.json({ error: preCallGuardrails.message }, { status: 400 });
}
body = preCallGuardrails.payload;   // may be redacted

// ... execute provider ...

const postCallGuardrails = await guardrailRegistry.runPostCallHooks(providerResponse, {
  log: requestLogger,
  disabledGuardrails,
});

```

Guardrails implement both `preCall` and `postCall` methods. **PII masking** runs in both stages (sanitizing request input and response output), while **prompt injection detection** runs only pre-call to block malicious prompts before they reach the model.

### Guardrail Priority and Execution Order

Guardrails execute by priority number—lower values run first:

| Guardrail | Priority | Stage(s) |
|-----------|----------|----------|
| PIIMaskerGuardrail | 10 | pre-call + post-call |
| PromptInjectionGuardrail | 20 | pre-call |
| VisionBridgeGuardrail | 30+ | pre-call |

This ordering ensures PII is stripped before injection analysis, preventing personal data from interfering with pattern detection.

## Resolving Disabled Guardrails

Before any guardrail runs, `resolveDisabledGuardrails` in [`src/lib/guardrails/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/guardrails/registry.ts) (lines 52-78) collects disable instructions from three sources:

```typescript
// src/lib/guardrails/registry.ts
export function resolveDisabledGuardrails({
  apiKeyInfo,
  body,
  headers,
}: {
  apiKeyInfo?: Record<string, unknown> | null;
  body?: unknown;
  headers?: HeadersLike;
}): string[] {
  const apiKeyDisabled = apiKeyInfo?.disabledGuardrails;
  
  const bodyRecord = body as Record<string, unknown>;
  const metadata = bodyRecord?.metadata as Record<string, unknown>;

  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);
}

```

Disable sources are merged and deduplicated. A typical result like `["pii-masker", "prompt-injection"]` prevents both guardrails from executing for that request.

## Prompt Injection Detection Implementation

The `PromptInjectionGuardrail` in [`src/lib/guardrails/promptInjection.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/guardrails/promptInjection.ts) provides configurable scanning of request content for attack patterns.

### Configuration Hierarchy

The guardrail reads settings from multiple layers, with later sources overriding earlier ones:

1. **Environment variables**: `INPUT_SANITIZER_MODE` (`block` | `warn` | `log`, default `"warn"`), `INPUT_SANITIZER_ENABLED`
2. **Feature flags**: `INJECTION_GUARD_MODE` from database (takes precedence over env)
3. **Runtime options**: passed to constructor

The options interface (lines 34-51) defines the full configuration surface:

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

```

### Detection Algorithm: `evaluatePromptInjection`

The core detection logic performs five steps:

1. **Built-in sanitization** – calls `sanitizeRequest` from `shared/utils/inputSanitizer`
2. **Content extraction** – flattens all message strings via `extractMessageContents`
3. **Bounded scan** – limits analysis to first `MAX_INJECTION_SCAN_BYTES` (~16 KB) for performance
4. **Pattern matching** – runs `detectWithPatterns` against default and custom regex patterns
5. **Severity evaluation** – combines results and determines block vs. flag vs. pass

The pattern matching implementation (lines 89-104):

```typescript
// src/lib/guardrails/promptInjection.ts
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 uses severity scoring (lines 118-127):

```typescript
// src/lib/guardrails/promptInjection.ts
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
  );
}

```

### Pre-Call Result Handling

The `preCall` method (lines 60-82) returns structured decisions:

```typescript
// src/lib/guardrails/promptInjection.ts
async preCall(
  payload: unknown,
  context: GuardrailContext
): Promise<GuardrailResult<unknown>> {
  const decision = evaluatePromptInjection(payload, this.options, context);
  
  if (decision.blocked) {
    return {
      block: true,
      message: "Request rejected: suspicious content detected",
      meta: {
        detections: decision.result.detections.length,
        piiDetections: decision.result.piiDetections.length
      },
    };
  }
  
  return {
    block: false,
    meta: decision.result.flagged
      ? { detections: decision.result.detections.length, piiDetections: decision.result.piiDetections.length }
      : null,
  };
}

```

When `block: true`, the chat handler aborts immediately with HTTP 400. In `warn` or `log` modes, execution continues with metadata attached for observability.

## PII Masking Implementation

The `PIIMaskerGuardrail` in [`src/lib/guardrails/piiMasker.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/guardrails/piiMasker.ts) provides deep, recursive redaction of personally identifiable information in both directions of the request-response cycle.

### Enablement Conditions

PII masking activates only when **both** environment variables are set:

```bash
PII_REDACTION_ENABLED=true
INPUT_SANITIZER_MODE=redact

```

This dual-gate design prevents accidental enabling and allows independent control of sanitization behavior.

### Pre-Call Redaction: `cloneAndMaskRequestPayload`

The guardrail creates a deep copy via `JSON.parse(JSON.stringify(payload))` to avoid mutating the original request. It then traverses known fields (`system`, `messages`, `input`, etc.) applying `processPII` to each string:

```typescript
// src/lib/guardrails/piiMasker.ts – lines 73-85
function cloneAndMaskRequestPayload(payload: unknown) {
  const clonedPayload: JsonRecord = JSON.parse(JSON.stringify(payload));
  // field-specific redaction logic...
  return { detections, modified, payload: modified ? clonedPayload : payload };
}

```

The method returns `{ modified: true, payload: redactedCopy }` when changes occur, or `{ modified: false, payload: original }` for passthrough efficiency.

### Post-Call Redaction: `sanitizePIIResponse`

After the provider responds, the post-call hook (lines 88-107) applies the same PII regexes to the response structure:

```typescript
// src/lib/guardrails/piiMasker.ts
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
  };
}

```

The `maskResponsesOutput` helper additionally walks any `output` arrays to redact embedded text content that may appear in non-standard provider response shapes.

## Registry Orchestration and Logging

The `GuardrailRegistry` in [`src/lib/guardrails/registry.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/lib/guardrails/registry.ts) provides the execution framework for all guardrails. Key responsibilities:

- **Registration**: Guardrails self-register via `register(guardrail: Guardrail)` with priority ordering
- **Pre-call execution**: `runPreCallHooks` iterates guardrails, skips disabled ones, aggregates modifications, and short-circuits on first block
- **Post-call execution**: `runPostCallHooks` applies response transformations in priority order

The registry emits structured debug logs for each guardrail execution (lines 49-55):

```typescript
// src/lib/guardrails/registry.ts
logger.debug?.(
  "GUARDRAIL",
  `${guardrail.name} pre-call ${execution.blocked ? "blocked" : modified ? "modified" : "passed"}`,
  meta || undefined
);

```

This logging enables audit trails and debugging of guardrail behavior in production.

## Configuration Examples

### Disabling Guardrails Per-Request

**HTTP header** (preferred):

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

{"messages": [{"role": "user", "content": "Hello"}]}

```

**Request body metadata**:

```json
{
  "metadata": { "disabledGuardrails": ["pii-masker"] },
  "messages": [{"role": "user", "content": "My SSN is 123-45-6789"}]
}

```

### Enabling Aggressive Injection Blocking

```bash

# Environment-level configuration

export INPUT_SANITIZER_MODE=block
export INJECTION_GUARD_MODE=block  # feature-flag override from database

```

### Custom Pattern Registration (Code)

```typescript
import { PromptInjectionGuardrail } from "@/lib/guardrails/promptInjection";

guardrailRegistry.register(
  new PromptInjectionGuardrail({
    blockThreshold: "medium",
    customPatterns: [
      /bypass\s+security/i,
      /ignore\s+previous\s+instructions/i,
      { pattern: /admin\s+password/i, severity: "high", name: "credential-leak" }
    ],
    mode: "block",
    priority: 5,  // run before standard guardrails
  })
);

```

## Summary

- **GuardrailRegistry** orchestrates all security checks through configurable pre-call and post-call hooks, with priority-based ordering and per-request disablement via headers, body metadata, or API key configuration.

- **PromptInjectionGuardrail** performs bounded 16 KB scans of request content using built-in patterns and custom regexes, with three operational modes (`block`, `warn`, `log`) and configurable severity thresholds (`low`, `medium`, `high`).

- **PIIMaskerGuardrail** creates deep-copied, redacted versions of payloads when `PII_REDACTION_ENABLED=true` and `INPUT_SANITIZER_MODE=redact`, applying to both incoming requests and outgoing responses without mutating originals.

- **Configuration layering** allows environment variables, database feature flags, and runtime options to control guardrail behavior, with explicit disable mechanisms for testing and emergency bypass.

## Frequently Asked Questions

### How does OmniRoute prioritize which guardrail runs first?

OmniRoute uses numeric priority values where lower numbers execute first. The default configuration assigns PIIMaskerGuardrail priority 10, PromptInjectionGuardrail priority 20, and VisionBridgeGuardrail priority 30+. This ensures PII is stripped before injection analysis runs, preventing personal data from interfering with attack pattern detection.

### Can prompt injection detection block requests entirely, or only log warnings?

The PromptInjectionGuardrail supports three modes controlled by `INPUT_SANITIZER_MODE` or `INJECTION_GUARD_MODE`: `block` rejects the request with HTTP 400 when severity thresholds are met; `warn` allows execution but attaches detection metadata; and `log` records findings silently without modifying the response. The default is `warn` for fail-open safety.

### What performance limits does OmniRoute apply to injection scanning?

OmniRoute bounds injection detection to the first `MAX_INJECTION_SCAN_BYTES` (approximately 16 KB) of extracted message content. This limit prevents denial-of-service through maliciously large payloads while covering typical chat completion requests. The scanning uses standard JavaScript `RegExp.match` operations against normalized pattern entries.

### How do I completely disable PII masking for a specific API key?

Add `"pii-masker"` to the `disabledGuardrails` array in the API key's metadata stored in the database, or pass `x-omniroute-disabled-guardrails: pii-masker` in request headers. Note that PII masking also requires `PII_REDACTION_ENABLED=true` and `INPUT_SANITIZER_MODE=redact` to activate—disabling the guardrail alone is sufficient to prevent redaction regardless of environment settings.