How OmniRoute's Guardrails System Protects AI Requests: PII Masking and Injection Defense
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 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. Every HTTP request traverses the following path:
- Resolution –_disabled guardrails are identified from headers, metadata, or API key settings.
- Pre-call execution – registered guardrails execute
preCallmethods to inspect and modify the payload. - Downstream execution – the sanitized request reaches LLM providers (OpenAI, Anthropic, etc.).
- Post-call execution – guardrails execute
postCallmethods 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 legacyx-disabled-guardrails) - Request body:
metadata.disabledGuardrailsarray - API key metadata: database-stored
disabledGuardrailscolumn
// 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 invokes the registry at approximately line 415:
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:
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 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 |
// 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:
- Calls
sanitizeRequestfor built-in injection and PII detection - Flattens message contents via
extractMessageContents - Runs regex patterns against
detectWithPatterns - Combines and deduplicates results
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:
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:
{
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 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:
// 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:
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 demonstrates the guardrails system integration:
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:
POST /v1/chat/completions
x-omniroute-disabled-guardrails: pii-masker,prompt-injection
Alternatively, via request metadata:
{
"metadata": {
"disabledGuardrails": ["prompt-injection"]
},
"messages": []
}
Environment Variables and Feature Flags
Enable aggressive blocking and PII redaction globally:
# 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:
guardrailRegistry.register(
new PromptInjectionGuardrail({
blockThreshold: "medium",
customPatterns: [/secret\s+token/i, "DROP TABLE"],
mode: "block",
priority: 5,
})
);
Summary
- Modular execution: The
GuardrailRegistryinsrc/lib/guardrails/registry.tsorchestrates 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=blockto 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.
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 →