# How OmniRoute Error Response Sanitization Prevents Stack Trace Leakage

> OmniRoute sanitizes error responses to prevent stack trace leakage by stripping sensitive data, replacing paths, and redacting credentials before client delivery. Secure your application.

- Repository: [Diego Rodrigues de Sa e Souza/OmniRoute](https://github.com/diegosouzapw/OmniRoute)
- Tags: security-best-practices
- Published: 2026-08-20

---

**OmniRoute prevents stack trace leakage through a mandatory sanitization pipeline that strips multi-line error details, replaces absolute paths with placeholders, and redacts credentials before any error reaches the client.**

OmniRoute is an open-source AI gateway that proxies requests between clients and upstream LLM providers. Because it handles sensitive authentication tokens and operates as a security boundary, **error response sanitization** is enforced at every layer of the stack. According to the OmniRoute source code, no raw `err.stack` or `err.message` ever leaves the server without passing through purpose-built sanitizers.

## Core Sanitization Functions in [`open-sse/utils/error.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/error.ts)

The sanitization layer centers on three utilities implemented in [[`open-sse/utils/error.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/error.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/utils/error.ts). Each addresses a specific attack vector for information disclosure.

### `sanitizeErrorMessage`: String-Level Scrubbing

**`sanitizeErrorMessage`** processes raw error strings to remove stack traces and filesystem paths. The implementation follows a strict, linear-time algorithm to avoid ReDoS vulnerabilities:

- **Truncates after first newline** (lines 66-68): Anything beyond the first line—where stack traces begin—is discarded.
- **Caps length at 4 KB** (line 22): Prevents memory exhaustion from pathological inputs.
- **Replaces absolute paths** (lines 26-73): Tokens matching POSIX paths (`/home/...`) or Windows paths (`C:\...`) become `"<path>"`.
- **Redacts credential patterns** (lines 42-53): Regex-based removal of data URLs, Bearer tokens, and API keys.

This function operates in **O(n)** time using simple string splitting rather than recursive regex replacement, ensuring attackers cannot trigger catastrophic backtracking.

### `sanitizeUpstreamDetails`: Recursive JSON Scrubbing

**`sanitizeUpstreamDetails`** handles structured error payloads from upstream providers. It recursively traverses JSON objects with defensive limits:

- Strings are delegated to `sanitizeErrorMessage`.
- Blacklisted keys (`stack`, `trace`, `path`, `password`, `token`, etc.) are **omitted entirely** (line 78).
- **Maximum depth of 4 levels** and **maximum array length of 32 elements** prevent deeply-nested payload attacks.
- Non-serializable values return `null` rather than throwing.

### `buildErrorBody`: Standardized Error Construction

**`buildErrorBody`** assembles the final JSON response returned to API clients (lines 121-146). It guarantees:

- Mandatory `sanitizeErrorMessage` call with fallback to generic message if sanitization yields empty string (line 28).
- Optional `upstream_details` field processed through `sanitizeUpstreamDetails`.
- **OpenAI-compatible error shape** for predictable client behavior without internal data exposure.

## Where Sanitization Is Enforced

OmniRoute applies these sanitizers at every error emission point. The repository's test suite ([`tests/unit/route-error-sanitization-v382.test.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/tests/unit/route-error-sanitization-v382.test.ts)) programmatically verifies that every route file imports and uses `sanitizeErrorMessage` or `buildErrorBody` in catch blocks.

### MCP / MitM Proxy Layer

In [[`src/mitm/tproxy/tlsCapture.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/mitm/tproxy/tlsCapture.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/mitm/tproxy/tlsCapture.ts#L215), errors from TLS interception are sanitized before attachment to SSE payloads:

```typescript
// Line 215: error sanitized before client exposure
const safeError = sanitizeErrorMessage(captureErr);

```

### API Route Handlers

Each Next.js API route sanitizes before responding. In [[`src/app/api/v1/usage/route.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/src/app/api/v1/usage/route.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/src/app/api/v1/usage/route.ts#L45-L50):

```typescript
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";

// Around lines 45-50
} catch (e) {
  const message = sanitizeErrorMessage(e) || "Internal server error";
  return NextResponse.json({ error: message }, { status: 500 });
}

```

### Upstream Provider Error Forwarding

[[`open-sse/utils/upstreamErrorPassthrough.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/upstreamErrorPassthrough.ts)](https://github.com/diegosouzapw/OmniRoute/blob/release/v3.8.50/open-sse/utils/upstreamErrorPassthrough.ts) uses `buildErrorBody` to wrap provider errors without leaking their internal details.

## Practical Implementation Examples

### Sanitizing a Caught Error Manually

```typescript
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error";

try {
  await riskyOperation();
} catch (err) {
  // Only first line, no paths, no credentials
  const safeMsg = sanitizeErrorMessage(err);
  logger.error({ safeMsg }, "Operation failed");
}

```

### Building a Sanitized HTTP Response

```typescript
import { buildErrorBody } from "@omniroute/open-sse/utils/error";

export async function GET(req: Request) {
  try {
    return await handleRequest(req);
  } catch (e) {
    const body = buildErrorBody(
      500,
      e,
      undefined,
      { type: "internal_error" }
    );
    return new Response(JSON.stringify(body), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

```

### Forwarding Provider Errors Safely

```typescript
import { buildErrorBody } from "@omniroute/open-sse/utils/error";

async function forwardProviderError(upstreamResp: Response) {
  const upstreamJson = await upstreamResp.json();
  const body = buildErrorBody(
    upstreamResp.status,
    upstreamJson.error?.message ?? "Provider error",
    upstreamJson, // sanitized via sanitizeUpstreamDetails
  );
  return new Response(JSON.stringify(body), { 
    status: upstreamResp.status 
  });
}

```

## Why This Architecture Stops Leakage

| Attack Vector | Mitigation |
|-------------|-----------|
| **Stack traces** | First-line truncation removes all frame information |
| **File system enumeration** | Absolute path replacement with `"<path>"` |
| **Credential exfiltration** | Regex-based redaction of tokens, keys, data URLs |
| **DoS via oversized errors** | 4 KB length cap, depth-4 recursion limit, 32-element array cap |
| **Inconsistent handling** | Mandatory test coverage enforces sanitizer usage in all catch blocks |

These measures implement **Hard Rule #12** from the OmniRoute security policy: raw `err.stack` and `err.message` are forbidden in external responses.

## Summary

- **`sanitizeErrorMessage`** in [`open-sse/utils/error.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/error.ts) strips stack traces, paths, and credentials from raw error strings using linear-time processing.
- **`sanitizeUpstreamDetails`** recursively scrubs JSON payloads with depth limits and key blacklisting.
- **`buildErrorBody`** constructs standardized, sanitized responses matching OpenAI's error format.
- Sanitizers are mandatory at MitM proxies, API routes, and upstream error forwarding paths.
- Automated tests verify every route uses sanitizers, preventing regression.

## Frequently Asked Questions

### What happens if sanitizeErrorMessage receives an extremely long error?

The function caps input at **4 KB** (line 22 in [`open-sse/utils/error.ts`](https://github.com/diegosouzapw/OmniRoute/blob/main/open-sse/utils/error.ts)). Anything beyond this limit is truncated before processing, preventing memory exhaustion attacks.

### Does OmniRoute ever log full stack traces internally?

While external responses are strictly sanitized, the source code shows that internal logging may retain full error details. The sanitization boundary is at the network edge—anything crossing to a client must pass through `sanitizeErrorMessage` or `buildErrorBody`.

### How does sanitizeUpstreamDetails handle non-JSON error responses?

Non-serializable values return `null` rather than throwing or passing through raw. This ensures that unexpected upstream payload shapes cannot bypass sanitization by causing deserialization failures.

### Can I disable error sanitization for debugging in development?

The source code does not expose a configuration flag to disable sanitization. Hard Rule #12 applies universally. For debugging, operators must inspect internal logs rather than client-facing responses.