Error Message Sanitization
v3.8.1Last updated: 2026-05-14
Was this page helpful?
Loading OmniRoute...
Source of truth: β , ,
Tests:
Last updated: 2026-05-14 β v3.8.0
Audience: Any engineer touching error responses (HTTP routes, SSE streams, executors, MCP handlers).
Status: MANDATORY for every code path that returns an error message to a client.
(CWE-209) flags any code path where an error message originating from a runtime exception reaches an HTTP / SSE response without being sanitized. Stack traces and absolute file paths in production responses give attackers:
helper in strips both classes of leakage:
) β replaced with .β sanitization is built-in:
import { buildErrorBody } from "@omniroute/open-sse/utils/error.ts";
export async function POST(req: Request) {
try {
// ... handler logic ...
} catch (err) {
return new Response(JSON.stringify(buildErrorBody(500, String(err))), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
}
import {
errorResponse, // one-shot Response object
writeStreamError, // SSE writer
createErrorResult, // { success: false, status, response, ... } shape
unavailableResponse, // adds Retry-After
providerCircuitOpenResponse,
modelCooldownResponse,
} from "@omniroute/open-sse/utils/error.ts";
and therefore through . You never need to call manually when using these helpers.
directly:
import { sanitizeErrorMessage } from "@omniroute/open-sse/utils/error.ts";
const body = JSON.stringify({
error: {
message: sanitizeErrorMessage(rawMessage),
type: "invalid_request_error",
code: "",
},
});
for the reference implementation.
should only wrap the value that crosses the network boundary. Internal logs (, ) should keep the full message, including stack, so operators can debug. Pattern:
try {
// ...
} catch (err) {
log.error({ err }, "handler failed"); // full err with stack β internal log
return errorResponse(500, getErrorMessage(err)); // sanitized β sent to client
}
Never put raw exception output in a Response body:
// BAD: stack trace + file paths reach the client
return new Response(JSON.stringify({ error: { message: err.stack || err.message } }), {
status: 500,
});
Never roll your own first-line splitter:
// BAD: forgets to strip absolute paths, may drift from the canonical helper
const safe = String(err).split("\n")[0];
Never sanitize in the route and forget the SSE path. Anything that writes to a stream goes through (or its underlying ).
Never include , , , env-derived paths in error messages β they bypass the path regex and reveal the deployment topology.
enforces:
.// instance inputs safely. field.) enforces β₯75% statements/lines/functions and β₯70% branches β error paths must be covered.
should always be either fixed via these helpers or dismissed with a comment citing this doc.
- redaction config (
β if present) handles structured log redaction separately. This doc covers only the response-message surface.
- ) covers header leakage β keep both files aligned when adding a new exfiltration concern.
accepts an optional third argument (raw
parsed body from the upstream provider). When provided, it is sanitized by
before inclusion in the response as .
:
call sites in pass
. Internal OmniRoute errors (SSE parse failures, empty content,
guardrail blocks) do not include .
, , or any string from a runtime exception to
. Those must still go through /
without an upstream body.
uses a fixed allowlist of sanitizer patterns (e.g. inline , with specific regex shapes, access to on ). It does not recognize indirection through a custom helper like our .
and β may continue to raise the alert even though the code is functionally safe. Precedent dismissals: , (May 2026), both marked with technical justification.
How to handle a new occurrence:
/ one of the wrappers documented above (read the call chain end-to-end β don't trust a comment).
- exercises the path (or add coverage).
- referencing this doc.
- not "fix" by inlining
everywhere β the helper is the single source of truth; duplicating the pattern weakens the sanitizer (loses path scrubbing, length cap, type coercion) for the appearance of placating the scanner.
custom sanitizer config is the long-term fix; it lives outside this doc.