fix: sanitize non-Latin1 chars in combo diagnostic headers (#6612) (#7190)

This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-07-14 21:19:16 -03:00
committed by GitHub
parent d84ccbc67c
commit de2b464c3f
3 changed files with 62 additions and 5 deletions

View File

@@ -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)

View File

@@ -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)),
},
});
}

View File

@@ -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);
});