How the Prompt Injection Guard Works Across All LLM Routes in OmniRoute
OmniRoute protects every inbound LLM request with a unified prompt injection guard that runs before the request reaches the model executor, using a three-layer middleware system that is configurable via feature flags and request headers.
The OmniRoute repository (diegosouzapw/OmniRoute) implements a comprehensive defense mechanism against prompt injection attacks across all its API endpoints. Whether you are hitting the chat completions, embeddings, images, or audio generation routes, the same guard evaluates every request body for malicious patterns before forwarding to the underlying LLM provider.
Architecture Overview
The prompt injection defense consists of three coordinated layers that provide a unified, configurable security perimeter:
| Layer | Responsibility | Source File |
|---|---|---|
| Middleware façade | Wraps every Next.js API route and invokes the guard on the parsed request body | src/middleware/promptInjectionGuard.ts:52 |
| Guardrail core | Performs detection, scoring, and blocking based on regex patterns and thresholds | src/lib/guardrails/promptInjection.ts:62-84 |
| Feature-flag registry | Resolves opt-out settings from headers, API keys, and request metadata | src/lib/guardrails/registry.ts:70-78 |
Middleware Layer: The withInjectionGuard Wrapper
Every LLM route in OmniRoute imports the withInjectionGuard higher-order function to automatically secure its handler. This wrapper ensures consistent protection across /v1/chat/completions, /v1/images/generations, and all other endpoints.
import { withInjectionGuard } from "@/middleware/promptInjectionGuard";
export const POST = withInjectionGuard(postHandler);
When a request arrives, the middleware executes the following sequence:
- Clones the Request – Creates a copy of the incoming request so the body can be read without destroying the original stream (lines 65-67 in
promptInjectionGuard.ts). - Parses the payload – Converts the JSON body into a JavaScript object.
- Invokes the guard – Calls the injection guard created by
createInjectionGuardwith the parsed body. - Blocks or flags – If the guard returns
blocked: true, the middleware immediately returns a 400 Response (lines 72-84). If merely flagged, it addsX-Injection-FlaggedandX-Injection-Detectionsheaders to the response (lines 86-90). - Passes pre-parsed data – The original handler receives the parsed body as a third argument, eliminating redundant JSON parsing (lines 100-104).
This design ensures that every LLM route shares the same security entry point, regardless of the specific model or provider being targeted.
Detection Engine: evaluatePromptInjection
The core detection logic resides in src/lib/guardrails/promptInjection.ts within the evaluatePromptInjection function. This engine coordinates multiple sanitization and scanning strategies to determine whether a request should proceed.
Enablement and Configuration
The guard first checks whether it should run at all:
- Environment variable: Respects
INPUT_SANITIZER_ENABLEDand theenabledoption (lines 58-60). - Mode resolution: Determines behavior via
getMode(lines 42-49), prioritizing:- Per-request options
- Database feature flag
INJECTION_GUARD_MODE - Environment variable
INJECTION_GUARD_MODE - Default value of
"warn"
- Threshold setting: Uses
getThreshold(lines 34-56) to establish the severity level required for blocking, defaulting to"high".
Pattern Matching and Sanitization
The guard collects and normalizes detection patterns before scanning:
- Pattern collection – Merges built-in
DEFAULT_GUARD_PATTERNSwith anycustomPatternssupplied by the caller, normalizing each into{name, pattern, severity}objects (lines 61-84). - Input sanitization – Runs the shared
sanitizeRequestutility to catch baseline injection patterns, then extracts all message strings usingextractMessageContents(lines 85-90). - Custom scanning – Scans only the first 16 KB of concatenated message text using
detectWithPatternsto prevent performance degradation (lines 93-98). - Detection merging – Appends any new custom detections not already caught by the sanitizer (lines 200-207).
Decision Logic
The final decision follows this priority:
- No detections – Request is allowed to proceed.
- Block mode – If
mode === "block"andshouldBlockreturns true (severity ≥ threshold), the request is blocked with a warning log (lines 20-28). - Warn/Log mode – Logs at the appropriate level but only blocks on
"warn"when a high-severity match exists (lines 30-44).
The function returns { blocked, result }, where result contains the full detection metadata for downstream inspection.
Configuration and Opt-Out Mechanisms
OmniRoute provides multiple pathways to disable or configure the prompt injection guard at runtime, managed by resolveDisabledGuardrails in src/lib/guardrails/registry.ts:70-78.
Disabling sources (in order of precedence):
- API-key metadata
- Request body
metadata.disabledGuardrails - Request body top-level
disabledGuardrails - Request header
X-Omniroute-Disabled-Guardrails(orX-Disabled-Guardrails)
To bypass the guard for a single request:
curl -X POST https://example.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "X-Omniroute-Disabled-Guardrails: prompt-injection" \
-d '{"model":"gpt-4o","messages":[{"role":"user","content":"test"}]}'
Feature-flag overrides – The database flag INJECTION_GUARD_MODE takes precedence over environment variables, allowing dynamic reconfiguration without redeployment.
Integration with the Guardrail Registry
The prompt injection guard registers itself with the central GuardrailRegistry alongside other protections like PII masking and Vision bridge validation. During request processing, the registry executes pre-call hooks for all enabled guardrails not listed in disabledGuardrails.
The PromptInjectionGuardrail class implements its preCall method by forwarding to evaluatePromptInjection (lines 60-62). With a default priority of 20, it runs early in the pipeline to ensure malicious payloads never reach downstream executors.
registry.register(new PromptInjectionGuardrail({
mode: "block",
blockThreshold: "medium"
}));
Implementation Examples
Wrapping a Custom Route
To apply the guard to a new endpoint:
import { withInjectionGuard } from "@/middleware/promptInjectionGuard";
async function myHandler(request, ctx, preParsedBody) {
// preParsedBody is already parsed JSON
const { model, messages } = preParsedBody;
// ... execute against LLM
return new Response(JSON.stringify({ result }));
}
export const POST = withInjectionGuard(myHandler);
Adding Custom Patterns at Runtime
import { PromptInjectionGuardrail } from "@/lib/guardrails/promptInjection";
const customGuard = new PromptInjectionGuardrail({
customPatterns: [
{ name: "evil_prompt", pattern: /run\s+.*\bcode\b/i, severity: "high" }
],
mode: "block",
blockThreshold: "high"
});
registry.register(customGuard);
This configuration blocks any request containing phrases matching "run ... code" regardless of the default pattern set.
Summary
- Universal coverage: The
withInjectionGuardmiddleware automatically protects every LLM route in OmniRoute, from chat completions to image generation. - Layered detection: Combines built-in sanitization, custom regex patterns, and configurable severity thresholds to identify prompt injection attempts.
- Flexible control: Supports runtime disabling via headers, API keys, or feature flags without code changes.
- Performance conscious: Limits text scanning to 16 KB and passes pre-parsed bodies to avoid redundant JSON parsing.
- Registry integration: Participates in the central guardrail system with priority 20, ensuring early execution in the request lifecycle.
Frequently Asked Questions
How do I completely disable the prompt injection guard for testing?
Send the X-Omniroute-Disabled-Guardrails: prompt-injection header with your request, or include "disabledGuardrails": ["prompt-injection"] in the request body metadata. Alternatively, configure your API key metadata to disable the guard for specific keys. According to src/lib/guardrails/registry.ts:70-78, these sources are aggregated to determine which guardrails to skip.
What is the difference between "block" and "warn" modes?
In "block" mode, the guard returns a 400 response immediately when the detection severity meets or exceeds the configured threshold. In "warn" mode, the request proceeds but adds X-Injection-Flagged headers to the response, and logs the detection. The exception is high-severity matches in "warn" mode, which still block to prevent obvious attacks. This logic is implemented in src/lib/guardrails/promptInjection.ts:20-28.
Can I add my own detection patterns without modifying the source code?
Yes. Instantiate PromptInjectionGuardrail with a customPatterns array containing objects with name, pattern (RegExp), and severity ("low", "medium", or "high"). Register this instance with the GuardrailRegistry to apply your custom rules alongside the defaults.
Why is there a 16 KB limit on text scanning?
The guard limits scanning to the first 16 KB of concatenated message content to maintain consistent performance across large payloads. This matches the limit used by the shared input sanitizer in src/shared/utils/inputSanitizer.ts, ensuring predictable behavior between the baseline sanitization and custom pattern detection.
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 →