How OmniRoute Error Response Sanitization Prevents Stack Trace Leakage
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
The sanitization layer centers on three utilities implemented in [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
nullrather than throwing.
buildErrorBody: Standardized Error Construction
buildErrorBody assembles the final JSON response returned to API clients (lines 121-146). It guarantees:
- Mandatory
sanitizeErrorMessagecall with fallback to generic message if sanitization yields empty string (line 28). - Optional
upstream_detailsfield processed throughsanitizeUpstreamDetails. - 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) 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/release/v3.8.50/src/mitm/tproxy/tlsCapture.ts#L215), errors from TLS interception are sanitized before attachment to SSE payloads:
// 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/release/v3.8.50/src/app/api/v1/usage/route.ts#L45-L50):
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/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
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
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
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
sanitizeErrorMessageinopen-sse/utils/error.tsstrips stack traces, paths, and credentials from raw error strings using linear-time processing.sanitizeUpstreamDetailsrecursively scrubs JSON payloads with depth limits and key blacklisting.buildErrorBodyconstructs 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). 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.
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 →