How OmniRoute's Guardrails System with PII Masking and Prompt Injection Protection Works
OmniRoute's guardrails system intercepts every request through pre‑call and post‑call hooks to redact personally identifiable information (PII) and detect prompt‑injection attacks before they reach the LLM provider.
The OmniRoute repository (diegosouzapw/OmniRoute) implements a modular safety framework that automatically sanitizes incoming requests and outgoing responses. This system runs as a middleware layer in the request pipeline, ensuring that sensitive data is masked and malicious prompts are blocked before execution.
Guardrail Pipeline Architecture
The guardrails framework operates as a registry-based pipeline that wraps every chat completion request. Located in src/lib/guardrails/registry.ts, the GuardrailRegistry orchestrates three core components: the PIIMaskerGuardrail, PromptInjectionGuardrail, and optional vision bridge guards.
The execution flow follows this sequence:
- Pre‑call hooks modify or block the incoming payload
- Provider execution (OpenAI, Anthropic, etc.)
- Post‑call hooks sanitize the upstream response
Guardrails execute in priority order: PII Masker (priority 10) runs first to strip sensitive data, followed by Prompt Injection (priority 20), then other guards. This ensures PII removal happens before injection analysis.
Resolving Disabled Guardrails
Guardrails can be disabled at three levels: API-key metadata, request body metadata, or HTTP headers. The resolveDisabledGuardrails function in src/lib/guardrails/registry.ts aggregates these sources:
// src/lib/guardrails/registry.ts – lines 52-78
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 stored in the GuardrailContext and passed to the registry's hook runners.
Prompt Injection Detection
The PromptInjectionGuardrail in src/lib/guardrails/promptInjection.ts scans request text for dangerous patterns using configurable severity thresholds.
Configuration Options
The guardrail supports three operating modes controlled via environment variables and runtime options:
INPUT_SANITIZER_MODE:"block","warn", or"log"(default:"warn")INPUT_SANITIZER_ENABLED:"false"disables the guardrail entirelyINJECTION_GUARD_MODE: Database feature-flag override that takes precedence over environment variables
Runtime options include blockThreshold ("low", "medium", "high"), customPatterns, and priority.
Detection Logic and Thresholds
The evaluatePromptInjection function performs a bounded scan of the first 16 KB (MAX_INJECTION_SCAN_BYTES) of message content:
// 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;
}
The shouldBlock function compares detection severity against the threshold:
// src/lib/guardrails/promptInjection.ts – lines 118-127
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);
}
If mode is "block" and severity exceeds the threshold, the guardrail returns block: true, causing the chat handler to abort with a 400 response before reaching the LLM provider.
PII Masking Implementation
The PIIMaskerGuardrail in src/lib/guardrails/piiMasker.ts performs deep-copy redaction on both requests and responses.
Pre‑Call Redaction
When PII_REDACTION_ENABLED equals "true" and INPUT_SANITIZER_MODE equals "redact", the guardrail clones the payload and sanitizes string fields:
// src/lib/guardrails/piiMasker.ts – lines 73-85
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)) {
// iterate and redact each message
}
return { detections, modified, payload: modified ? clonedPayload : payload };
}
The modified payload replaces the original before downstream execution.
Post‑Call Redaction
After receiving the provider response, the guardrail runs sanitizePIIResponse to redact any PII in the model output:
// src/lib/guardrails/piiMasker.ts – lines 88-107
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
};
}
Integration in the Chat Handler
The core chat handler in src/sse/handlers/chat.ts ties the guardrails together:
// src/sse/handlers/chat.ts – line ~415
const disabledGuardrails = resolveDisabledGuardrails({ apiKeyInfo, body, headers });
const preCallGuardrails = await guardrailRegistry.runPreCallHooks(body, {
log: requestLogger,
disabledGuardrails,
});
if (preCallGuardrails.blocked) {
return NextResponse.json({ error: preCallGuardrails.message }, { status: 400 });
}
body = preCallGuardrails.payload; // sanitized copy
// Execute provider call
const providerResponse = await executeProvider(body);
// Post-call sanitization
const postCallGuardrails = await guardrailRegistry.runPostCallHooks(
providerResponse,
{ log: requestLogger, disabledGuardrails }
);
The registry logs each guardrail execution:
// src/lib/guardrails/registry.ts
logger.debug?.(
"GUARDRAIL",
`${guardrail.name} pre-call ${execution.blocked ? "blocked" : modified ? "modified" : "passed"}`
);
Configuration and Customization
Disabling Guardrails
Disable via HTTP header:
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 PII Masking
Set environment variables:
export PII_REDACTION_ENABLED=true
export INPUT_SANITIZER_MODE=redact
Tuning Injection Detection
Configure aggressive blocking:
export INPUT_SANITIZER_MODE=block
export INJECTION_GUARD_MODE=block
Or register custom patterns at runtime:
guardrailRegistry.register(
new PromptInjectionGuardrail({
blockThreshold: "medium",
customPatterns: [/secret\s+token/i, "DROP TABLE"],
mode: "block",
priority: 5,
})
);
Summary
- OmniRoute's guardrails system implements pre‑call and post‑call hooks in
src/lib/guardrails/registry.tsto intercept all requests. - Prompt injection protection scans the first 16 KB of message content for dangerous patterns, supporting
block,warn, andlogmodes with configurable severity thresholds. - PII masking performs deep-copy redaction on requests and responses when
PII_REDACTION_ENABLEDandINPUT_SANITIZER_MODEare properly configured. - Priority-based execution ensures PII masker (priority 10) runs before prompt injection (priority 20) to prevent sensitive data from affecting detection patterns.
- Flexible disabling works via headers, body metadata, API-key settings, or environment variables for granular control.
Frequently Asked Questions
How do I completely disable the prompt injection guardrail for a specific request?
Add the x-omniroute-disabled-guardrails header with value prompt-injection to your HTTP request. Alternatively, include "disabledGuardrails": ["prompt-injection"] in the request body's metadata object. Both methods are evaluated by the resolveDisabledGuardrails function in src/lib/guardrails/registry.ts.
What is the difference between "warn" and "block" mode in the injection guardrail?
Block mode rejects the request with a 400 status when shouldBlock returns true based on the severity threshold. Warn mode allows the request to proceed but logs the detection via the guardrail context logger. Log mode silently records detections without flagging. The mode is controlled by the INPUT_SANITIZER_MODE environment variable or INJECTION_GUARD_MODE database feature flag.
Why does the PII masker run before the prompt injection guardrail?
The PIIMaskerGuardrail is registered with priority 10, while PromptInjectionGuardrail uses priority 20. This ordering ensures that personal identifiers (emails, phone numbers, etc.) are redacted before injection detection runs, preventing false positives where PII content might match injection patterns and ensuring user privacy in the detection logs.
Can I add custom injection patterns without modifying the source code?
Yes. When registering the guardrail programmatically, pass an array of customPatterns to the PromptInjectionGuardrail constructor. Each pattern can be a RegExp or string that gets normalized via normalizePatternEntry and evaluated alongside built-in patterns in the detectWithPatterns function.
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 →