How OmniRoute Implements PII Masking, Prompt Injection Detection, and Vision Bridge Guardrails

OmniRoute implements PII masking, prompt injection detection, and vision bridge capabilities through a unified guardrail framework that intercepts requests before and after routing via the BaseGuardrail abstract class.

The open-source AI routing layer OmniRoute (diegosouzapw/OmniRoute) provides a pluggable guardrail framework to secure traffic against data leakage, adversarial inputs, and incompatible model capabilities. Every request passes through a central registry of validators that can block, modify, or annotate payloads based on configurable pre-call and post-call policies.

Guardrail Framework Architecture

All guardrails extend the abstract class BaseGuardrail defined in src/lib/guardrails/base.ts. Each implementation provides preCall and/or postCall methods that return a GuardrailResult specifying whether to block the request, modify the payload, or attach metadata for observability.

The guardrail registry (src/lib/guardrails/registry.ts) maintains a singleton list of active guards. During server initialization (src/server-init.ts), registerDefaultGuardrails() loads the three built-in guards into the registry. The chat handler (src/sse/handlers/chat.ts) then iterates through this registry before dispatching requests to upstream providers and again after receiving responses.

PII Masker Guardrail

The PII Masker guardrail (src/lib/guardrails/piiMasker.ts) redacts personally identifiable information from both requests and responses. It inherits from BaseGuardrail and runs with priority 10 (enabled by default).

Request-side processing uses cloneAndMaskRequestPayload() to recursively traverse the JSON payload, invoking sanitizePII() from src/lib/piiSanitizer.ts on every string value. If redaction occurs, the method returns a modified copy of the payload.

Response-side processing applies maskResponsesOutput() to iterate over output_text and output fields, calling sanitizePII() and sanitizePIIResponse() to ensure sensitive data never leaves the OmniRoute boundary.

// src/lib/guardrails/piiMasker.ts
export class PIIMaskerGuardrail extends BaseGuardrail {
  async preCall(payload, _context) {
    const result = cloneAndMaskRequestPayload(payload);
    return { block: false, modifiedPayload: result.payload };
  }

  async postCall(response, _context) {
    const sanitized = maskResponsesOutput(response);
    return { block: false, modifiedResponse: sanitized };
  }
}

Enable this guardrail by setting the environment variable PII_REDACTION_ENABLED=true and configuring INPUT_SANITIZER_MODE=redact.

Prompt Injection Guardrail

The Prompt Injection guardrail (src/lib/guardrails/promptInjection.ts) detects malicious system-override directives and adversarial patterns. It operates in three modes—block, warn, or log—controlled by the INJECTION_GUARD_MODE environment variable or database settings.

The core detection logic resides in evaluatePromptInjection():

  1. Sanitizes the payload using sanitizeRequest() from src/shared/utils/inputSanitizer.ts.
  2. Extracts raw message text via extractMessageContents().
  3. Scans the first 16 KB (MAX_INJECTION_SCAN_BYTES) against DEFAULT_GUARD_PATTERNS or user-supplied customPatterns.
  4. Aggregates severity scores and compares them against the blockThreshold (default "high").

The PromptInjectionGuardrail class wraps this evaluation:

// src/lib/guardrails/promptInjection.ts
export class PromptInjectionGuardrail extends BaseGuardrail {
  async preCall(payload, context) {
    const decision = evaluatePromptInjection(payload, this.options, context);
    if (decision.blocked) {
      return { block: true, message: "Request rejected: suspicious content detected" };
    }
    return { block: false, meta: decision.result.flagged ? { flagged: true } : null };
  }
}

Disable this guardrail for a specific request by including the header x-omniroute-disabled-guardrails: prompt-injection.

Vision Bridge Guardrail

When a request contains images but the target model lacks vision support, the Vision Bridge guardrail (src/lib/guardrails/visionBridge.ts) intercepts the payload, generates textual descriptions via a configured vision model, and replaces the image blocks with those descriptions. It runs at priority 5 and is enabled by default.

The decision logic getComboVisionBridgeDecision analyzes combo routing tables to determine if any target model requires image translation. If all targets are vision-capable, the guard skips processing.

In preCall(), the guardrail executes the following pipeline:

  1. Validates enable flags and the per-request disabling header.
  2. Extracts image parts using extractImageParts() from src/lib/guardrails/visionBridgeHelpers.ts.
  3. Limits processing to the configured maxImages to prevent throughput bottlenecks.
  4. Invokes callVision() for each image in parallel via Promise.allSettled.
  5. Replaces image blocks with descriptions using replaceImageParts(), or retains original images on failure.
// src/lib/guardrails/visionBridge.ts
if (imageParts.length === 0) return { block: false };
const results = await Promise.allSettled(
  limitedParts.map(async (p, i) => {
    const desc = await callVision(p.imageUrl, config);
    return `[Image ${i + 1}]: ${desc}`;
  })
);
const modifiedBody = replaceImageParts(body, descriptions);
return { block: false, modifiedPayload: modifiedBody, meta: { transformed: true } };

Configuration sources include the database settings table (src/lib/db/settings.ts) and defaults defined in src/shared/constants/visionBridgeDefaults.ts.

Wiring Guardrails into the Request Flow

The request lifecycle follows a strict pipeline:

  1. Registration: src/server-init.ts invokes registerDefaultGuardrails() to populate the singleton registry.
  2. Pre-call validation: In src/sse/handlers/chat.ts, the handler iterates through guardrailRegistry.list(), executing preCall() for each guard. If any guard returns block: true, OmniRoute returns an early error response. Modified payloads are forwarded to subsequent guards and ultimately to the upstream provider.
  3. Post-call sanitization: After receiving the upstream response, the handler invokes postCall() on each guardrail (e.g., for PII redaction on the response body) before streaming data to the client.

Feature flags such as INJECTION_GUARD_MODE and database-backed vision-bridge settings allow runtime toggling without redeployment.

Configuration and Usage Examples

Disabling guardrails per request:

// Disable prompt-injection detection for a single API call
const resp = await fetch("/api/chat/completions", {
  method: "POST",
  headers: { 
    "x-omniroute-disabled-guardrails": "prompt-injection" 
  },
  body: JSON.stringify({ 
    model: "gpt-4", 
    messages: [{ role: "user", content: "..." }] 
  })
});

Triggering the vision bridge automatically:

// Send an image to a non-vision model (e.g., GPT-3.5)
await fetch("/api/chat/completions", {
  method: "POST",
  body: JSON.stringify({
    model: "gpt-3.5-turbo",
    messages: [{ 
      role: "user", 
      content: [
        { type: "text", text: "Describe this:" },
        { type: "image_url", image_url: "data:image/png;base64,..." }
      ] 
    }]
  })
});

OmniRoute automatically invokes the vision model, replaces the image block with the generated description, and forwards the text-only payload to the upstream provider.

Inspecting guardrail metadata:

const result = await fetch("/api/chat/completions", { ... });
const data = await result.json();
console.log(data.guardrails); 
// Output: [{ name: "pii-masker", detections: 2, redacted: true }]

Summary

  • Architecture: OmniRoute uses a registry-based pattern where guards extending BaseGuardrail in src/lib/guardrails/base.ts process requests via preCall() and responses via postCall().
  • PII Masking: Implemented in src/lib/guardrails/piiMasker.ts, sanitizing both requests (cloneAndMaskRequestPayload) and responses (maskResponsesOutput) using the shared engine in src/lib/piiSanitizer.ts.
  • Prompt Injection: Detected in src/lib/guardrails/promptInjection.ts via evaluatePromptInjection(), scanning 16 KB of content against configurable patterns with modes: block, warn, or log.
  • Vision Bridge: Located in src/lib/guardrails/visionBridge.ts, translates images to text for non-vision models using extractImageParts(), callVision(), and replaceImageParts().
  • Integration: The pipeline in src/sse/handlers/chat.ts executes guards sequentially, respecting priority levels and the x-omniroute-disabled-guardrails header for selective bypass.

Frequently Asked Questions

How do I disable a specific guardrail for a single request?

Include the x-omniroute-disabled-guardrails header with a comma-separated list of guardrail names (e.g., "prompt-injection,vision-bridge"). The registry in src/lib/guardrails/registry.ts checks this header before executing any guard's preCall() method.

What happens when the prompt injection guardrail detects suspicious content?

Behavior depends on the configured mode. In "block" mode (set via INJECTION_GUARD_MODE env var), the guard returns block: true and OmniRoute rejects the request with a 400-level error. In "warn" or "log" modes, the request proceeds but metadata about the detection is attached to the response for audit trails.

How does the vision bridge handle failures when describing images?

The vision bridge uses Promise.allSettled() to process images in parallel. If an individual image fails to generate a description (network error or model failure), the guardrail retains the original image block in the payload rather than blocking the entire request, ensuring graceful degradation.

Can I add custom detection patterns for prompt injection?

Yes. Pass an array of regex patterns via the customPatterns option when configuring the PromptInjectionGuardrail. These patterns are merged with DEFAULT_GUARD_PATTERNS and evaluated alongside the built-in sanitization logic in evaluatePromptInjection().

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →