mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
This commit is contained in:
committed by
GitHub
parent
d84ccbc67c
commit
de2b464c3f
@@ -0,0 +1 @@
|
||||
- fix(sse): sanitize non-Latin1 characters before embedding combo diagnostics in HTTP headers, preventing a ByteString crash on quality-check failure (#6612)
|
||||
@@ -145,6 +145,22 @@ function clampDiagStr(v: unknown, max = 128): string {
|
||||
return typeof v === "string" ? v.slice(0, max).replace(/[\r\n]+/g, " ") : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* HTTP header values must be Latin1/ByteString (undici throws a TypeError
|
||||
* otherwise — see #6612). Replace any codepoint outside the Latin1 range
|
||||
* (0-255) with "?" so header construction never throws. Only used for the
|
||||
* literal header value; the JSON body keeps the original, unsanitized
|
||||
* readable text via `sanitizeComboDiagnostics`.
|
||||
*/
|
||||
function toHeaderSafeAscii(v: string): string {
|
||||
let out = "";
|
||||
for (let i = 0; i < v.length; i++) {
|
||||
const code = v.charCodeAt(i);
|
||||
out += code > 255 ? "?" : v[i];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whitelist projection — guarantees only id/reason string primitives + integer
|
||||
* counts can escape, regardless of what the caller assembled. This is the secret
|
||||
@@ -186,10 +202,12 @@ export function errorResponseWithComboDiagnostics(
|
||||
if (opts.code) body.error.code = opts.code;
|
||||
if (opts.type) body.error.type = opts.type;
|
||||
body.diagnostics = safe;
|
||||
const excludedHeader = safe.excluded
|
||||
.map((e) => `${e.provider}${e.model ? `/${e.model}` : ""}:${e.reason}`)
|
||||
.join(",")
|
||||
.slice(0, 900);
|
||||
const excludedHeader = toHeaderSafeAscii(
|
||||
safe.excluded
|
||||
.map((e) => `${e.provider}${e.model ? `/${e.model}` : ""}:${e.reason}`)
|
||||
.join(",")
|
||||
.slice(0, 900)
|
||||
);
|
||||
return new Response(JSON.stringify(body), {
|
||||
status: statusCode,
|
||||
headers: {
|
||||
@@ -197,7 +215,7 @@ export function errorResponseWithComboDiagnostics(
|
||||
"x-omniroute-combo-pool-size": String(safe.poolSize),
|
||||
"x-omniroute-combo-attempted": String(safe.attempted),
|
||||
"x-omniroute-combo-excluded": excludedHeader,
|
||||
"x-omniroute-combo-terminal-reason": safe.terminalReason.slice(0, 200),
|
||||
"x-omniroute-combo-terminal-reason": toHeaderSafeAscii(safe.terminalReason.slice(0, 200)),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -79,3 +79,41 @@ test("combo diagnostics: secret containment — non-whitelisted fields never sur
|
||||
assert.ok(!serialized.includes("accessToken"), "no accessToken KEY survives");
|
||||
assert.ok(!serialized.includes("token"), "no token KEY survives");
|
||||
});
|
||||
|
||||
test("combo diagnostics: terminalReason with a non-Latin1 char (em dash) must not crash Response construction (#6612)", () => {
|
||||
const terminalReason = "reasoning consumed 5/5 tokens — no content output";
|
||||
assert.doesNotThrow(() => {
|
||||
const res = errorResponseWithComboDiagnostics(
|
||||
502,
|
||||
`Upstream response failed quality validation: ${terminalReason}`,
|
||||
{
|
||||
poolSize: 4,
|
||||
attempted: 1,
|
||||
excluded: [{ provider: "deepseek", model: "deepseek-v4-flash-free", reason: "quality — bad" }],
|
||||
attemptOrder: [{ provider: "deepseek", model: "deepseek-v4-flash-free" }],
|
||||
terminalReason,
|
||||
}
|
||||
);
|
||||
assert.equal(res.status, 502);
|
||||
});
|
||||
});
|
||||
|
||||
test("combo diagnostics: JSON body keeps the original non-Latin1 text even though headers are ASCII-sanitized (#6612)", async () => {
|
||||
const terminalReason = "reasoning consumed 5/5 tokens — no content output";
|
||||
const res = errorResponseWithComboDiagnostics(
|
||||
502,
|
||||
`Upstream response failed quality validation: ${terminalReason}`,
|
||||
{
|
||||
poolSize: 1,
|
||||
attempted: 1,
|
||||
excluded: [],
|
||||
attemptOrder: [{ provider: "deepseek", model: "deepseek-v4-flash-free" }],
|
||||
terminalReason,
|
||||
}
|
||||
);
|
||||
// Header value must be a valid Latin1 ByteString — em dash (U+2014) replaced.
|
||||
assert.equal(res.headers.get("x-omniroute-combo-terminal-reason"), terminalReason.replace("—", "?"));
|
||||
const body = await res.json();
|
||||
// JSON body keeps the original, readable (unsanitized) em dash.
|
||||
assert.equal(body.diagnostics.terminalReason, terminalReason);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user