How OmniRoute's Guardrails Framework Handles PII Masking and Prompt Injection Detection
OmniRoute's guardrails framework protects LLM requests through a pluggable pipeline that masks personally identifiable information (PII) in both requests and responses while detecting prompt injection attacks via configurable pattern matching, severity thresholds, and blocking modes.
The OmniRoute open-source routing layer for large language models implements a modular guardrails framework that intercepts every HTTP request before it reaches providers like OpenAI or Anthropic. Written in TypeScript, this system orchestrates pre-call sanitization and post-call filtering through a priority-based registry, ensuring sensitive data never leaks upstream while malicious prompts get blocked at the edge.
Architecture of the Guardrail Pipeline
The framework centers on the GuardrailRegistry class defined in src/lib/guardrails/registry.ts. This orchestrator maintains a registry of guardrail instances and executes them in two distinct phases:
- Pre-call hooks: Execute after request validation but before the downstream provider call
- Post-call hooks: Execute on the provider's response before returning to the client
The execution order follows a priority system where lower numbers run first. The PII Masker runs at priority 10, ensuring personal data is redacted before the Prompt Injection Guardrail (priority 20) analyzes the sanitized content for attack patterns.
How PII Masking Works in OmniRoute
The PII Masker Guardrail (src/lib/guardrails/piiMasker.ts) operates during both pipeline phases to redact personally identifiable information using deep-cloning and regex-based detection.
Pre-Call Request Sanitization
When PII_REDACTION_ENABLED equals "true" and INPUT_SANITIZER_MODE equals "redact", the guardrail invokes cloneAndMaskRequestPayload to create a sanitized copy of the request:
// src/lib/guardrails/piiMasker.ts – lines 73-85
function cloneAndMaskRequestPayload(payload: unknown) {
const clonedPayload: JsonRecord = JSON.parse(JSON.stringify(payload));
// Redacts system prompts, message arrays, and input fields
if (typeof clonedPayload.system === "string") { /* ... */ }
if (Array.isArray(clonedPayload.messages)) { /* ... */ }
return { detections, modified, payload: modified ? clonedPayload : payload };
}
This approach uses JSON.parse(JSON.stringify(payload)) to deep-clone the object, ensuring the original request remains immutable while strings are processed through processPII detection regexes.
Post-Call Response Redaction
After the LLM provider returns a response, the guardrail's postCall method in src/lib/guardrails/piiMasker.ts (lines 88-107) applies sanitizePIIResponse to the cloned response object. The maskResponsesOutput function additionally traverses any output arrays to redact embedded text, returning the sanitized payload via modifiedResponse.
Prompt Injection Detection and Blocking Mechanisms
The Prompt Injection Guardrail (src/lib/guardrails/promptInjection.ts) implements a bounded scanning engine that analyzes request content for dangerous patterns while respecting configurable severity thresholds.
Configuration Options
The guardrail supports three operating modes controlled via environment variables or runtime options:
INPUT_SANITIZER_MODE: Sets the default behavior (block,warn, orlog)INJECTION_GUARD_MODE: Overrides the environment setting via database feature flagsINPUT_SANITIZER_ENABLED: Global toggle to disable the guardrail entirely
Runtime configuration occurs through the PromptInjectionGuardrailOptions interface:
// src/lib/guardrails/promptInjection.ts – lines 34-51
export interface PromptInjectionGuardrailOptions {
blockThreshold?: "low" | "medium" | "high";
customPatterns?: PatternLike[];
enabled?: boolean;
mode?: "block" | "warn" | "log";
priority?: number;
}
Detection Engine Mechanics
The evaluatePromptInjection function performs a bounded scan limited to the first MAX_INJECTION_SCAN_BYTES (approximately 16 KB) for performance optimization. The detection flow follows these steps:
- Sanitization pass: Calls
sanitizeRequestfrom shared utilities to obtain baseline injection and PII detections - Content extraction: Flattens all message strings using
extractMessageContents - Pattern matching: Executes
detectWithPatternsagainst default and user-supplied regex patterns - Severity evaluation: The
shouldBlockfunction compares detection severities against theblockThresholdusingSEVERITY_SCORESmapping
// src/lib/guardrails/promptInjection.ts – lines 89-104
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
The guardrail returns a blocking decision when mode equals "block" and shouldBlock returns true based on the severity threshold comparison. When blocked, the chat handler in src/sse/handlers/chat.ts receives a GuardrailResult with block: true and aborts the request before any provider call occurs, returning HTTP 400 with the message "Request rejected: suspicious content detected".
Integrating Guardrails into the Request Flow
The core chat handler (src/sse/handlers/chat.ts, line ~415) orchestrates the guardrail execution:
import { guardrailRegistry, resolveDisabledGuardrails } from "@/lib/guardrails";
// Resolve which guardrails to skip based on headers, body, or API key
const disabledGuardrails = resolveDisabledGuardrails({ apiKeyInfo, body, headers });
// Execute pre-call hooks (PII masking → Injection detection)
const preCallGuardrails = await guardrailRegistry.runPreCallHooks(body, {
log: requestLogger,
disabledGuardrails,
});
if (preCallGuardrails.blocked) {
return NextResponse.json({ error: preCallGuardrails.message }, { status: 400 });
}
body = preCallGuardrails.payload; // Use potentially redacted payload
// ... execute provider call ...
// Execute post-call hooks (PII masking on response)
const postCallGuardrails = await guardrailRegistry.runPostCallHooks(providerResponse, {
log: requestLogger,
disabledGuardrails,
});
The resolveDisabledGuardrails function (lines 52-78 in src/lib/guardrails/registry.ts) aggregates disable flags from three sources: API key metadata (disabledGuardrails), request body (metadata.disabledGuardrails), and the HTTP header x-omniroute-disabled-guardrails (or legacy x-disabled-guardrails).
Configuring and Customizing Guardrails
Disabling Guardrails Per Request
You can disable specific guardrails via HTTP headers:
POST /v1/chat/completions
x-omniroute-disabled-guardrails: pii-masker,prompt-injection
Or via request body metadata:
{
"metadata": { "disabledGuardrails": ["prompt-injection"] },
"messages": [{ "role": "user", "content": "Hello" }]
}
Enabling Aggressive PII Redaction
Set these environment variables to enable full redaction:
export PII_REDACTION_ENABLED=true
export INPUT_SANITIZER_MODE=redact
Adding Custom Injection Patterns
When registering the guardrail programmatically, supply custom regex patterns:
guardrailRegistry.register(
new PromptInjectionGuardrail({
blockThreshold: "medium",
customPatterns: [/secret\s+token/i, "DROP TABLE"],
mode: "block",
priority: 5, // Run before standard guardrails
})
);
Summary
- OmniRoute's guardrails framework implements a dual-phase pipeline (pre-call and post-call) orchestrated by
GuardrailRegistryinsrc/lib/guardrails/registry.ts. - PII masking creates deep clones of payloads via
cloneAndMaskRequestPayloadinsrc/lib/guardrails/piiMasker.ts, redacting sensitive strings in both requests and responses whenPII_REDACTION_ENABLEDis true. - Prompt injection detection scans the first 16 KB of request content using
evaluatePromptInjectioninsrc/lib/guardrails/promptInjection.ts, supporting custom patterns and configurable severity thresholds (low, medium, high). - Execution priority ensures PII masking (priority 10) runs before injection detection (priority 20), preventing personal data from interfering with attack pattern matching.
- Flexible disabling allows per-request configuration through HTTP headers, body metadata, or API key settings, while global modes are controlled via environment variables.
Frequently Asked Questions
How do I disable PII masking for a specific API request?
Send the x-omniroute-disabled-guardrails header with the value pii-masker, or include "disabledGuardrails": ["pii-masker"] in the request body's metadata object. The resolveDisabledGuardrails function in src/lib/guardrails/registry.ts processes these values and excludes the guardrail from execution for that request.
What is the default behavior when prompt injection is detected?
By default, the Prompt Injection Guardrail operates in "warn" mode as defined by the INPUT_SANITIZER_MODE environment variable. In this mode, suspicious content is logged but the request proceeds to the provider. To block requests automatically, set INPUT_SANITIZER_MODE=block or configure blockThreshold to trigger on specific severity levels (low, medium, or high).
Can I use custom regex patterns for injection detection?
Yes, the PromptInjectionGuardrailOptions interface accepts a customPatterns array that supports both RegExp objects and string literals. These patterns are normalized through normalizePatternEntry and evaluated alongside built-in patterns in the detectWithPatterns function, allowing you to detect organization-specific sensitive keywords or attack signatures.
Why does PII masking run before prompt injection detection?
The PII Masker is assigned priority 10 while the Prompt Injection Guardrail uses priority 20 in the registry. This ordering ensures that personal information (like email addresses or phone numbers) is redacted before the injection detector analyzes the content. Without this sequence, legitimate PII patterns might trigger false positives in the injection detection logic, or malicious prompts could exfiltrate PII before blocking occurs.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →