Files
openclaw/scripts/e2e/lib/bounded-response-text.mjs
Vincent Koc 0f18e82932 fix(e2e): reject unsafe bounded response text lengths
Reject unsafe decimal Content-Length values in the E2E bounded response text helper before streaming response bodies. Keep non-decimal values on the streaming byte-limit path and add regression coverage proving unsafe declared lengths cancel without starting a read.

Proof: direct patched repro rejects before reading with code ETOOBIG; origin/main comparison entered the reader first; node --check scripts/e2e/lib/bounded-response-text.mjs; git diff --check origin/main...HEAD; autoreview clean overall 0.86; exact-head release gate succeeded at https://github.com/openclaw/openclaw/actions/runs/27846197115.
2026-06-20 04:20:02 +08:00

69 lines
1.9 KiB
JavaScript

// Bounded response body reader used by E2E HTTP fixture clients.
function bodyTooLargeError(label, byteLimit) {
return Object.assign(new Error(`${label} response body exceeded ${byteLimit} bytes`), {
code: "ETOOBIG",
});
}
function cancelReaderSoon(reader) {
void Promise.resolve()
.then(() => reader.cancel())
.catch(() => {});
}
function parseContentLengthHeader(headers) {
const raw = headers.get("content-length");
if (!raw || !/^\d+$/u.test(raw)) {
return undefined;
}
const parsed = Number(raw);
return Number.isSafeInteger(parsed) ? parsed : Number.POSITIVE_INFINITY;
}
export async function readBoundedResponseText(response, label, byteLimit, timeoutPromise) {
const contentLength = parseContentLengthHeader(response.headers);
if (contentLength !== undefined && contentLength > byteLimit) {
await response.body?.cancel().catch(() => {});
throw bodyTooLargeError(label, byteLimit);
}
if (!response.body) {
return "";
}
const reader = response.body.getReader();
const decoder = new TextDecoder();
let byteCount = 0;
let text = "";
let canceled = false;
try {
while (true) {
const read = reader.read();
const readWithTimeout = timeoutPromise
? Promise.race([
read,
timeoutPromise.catch((error) => {
canceled = true;
cancelReaderSoon(reader);
throw error;
}),
])
: read;
const { done, value } = await readWithTimeout;
if (done) {
return text + decoder.decode();
}
byteCount += value.byteLength;
if (byteCount > byteLimit) {
canceled = true;
await reader.cancel().catch(() => {});
throw bodyTooLargeError(label, byteLimit);
}
text += decoder.decode(value, { stream: true });
}
} finally {
if (!canceled) {
reader.releaseLock();
}
}
}