mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-31 07:14:53 +00:00
fix(e2e): bound ClawHub preflight response bodies
This commit is contained in:
@@ -8,6 +8,10 @@ const CLAWHUB_PREFLIGHT_TIMEOUT_MS = readPositiveInt(
|
||||
process.env.OPENCLAW_PLUGINS_E2E_CLAWHUB_PREFLIGHT_TIMEOUT_MS,
|
||||
30_000,
|
||||
);
|
||||
const CLAWHUB_PREFLIGHT_BODY_MAX_BYTES = readPositiveInt(
|
||||
process.env.OPENCLAW_PLUGINS_E2E_CLAWHUB_PREFLIGHT_BODY_MAX_BYTES,
|
||||
1024 * 1024,
|
||||
);
|
||||
const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
|
||||
const scratchFile = (name) => path.join(scratchRoot, name);
|
||||
|
||||
@@ -42,6 +46,47 @@ async function withTimeout(label, timeoutMs, run) {
|
||||
}
|
||||
}
|
||||
|
||||
function bodyTooLargeError(label, byteLimit) {
|
||||
return Object.assign(new Error(`${label} response body exceeded ${byteLimit} bytes`), {
|
||||
code: "ETOOBIG",
|
||||
});
|
||||
}
|
||||
|
||||
async function readBoundedResponseText(response, label, byteLimit) {
|
||||
const contentLength = response.headers.get("content-length");
|
||||
if (contentLength) {
|
||||
const parsedLength = Number(contentLength);
|
||||
if (Number.isSafeInteger(parsedLength) && parsedLength > 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 = "";
|
||||
try {
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) {
|
||||
return text + decoder.decode();
|
||||
}
|
||||
byteCount += value.byteLength;
|
||||
if (byteCount > byteLimit) {
|
||||
await reader.cancel().catch(() => {});
|
||||
throw bodyTooLargeError(label, byteLimit);
|
||||
}
|
||||
text += decoder.decode(value, { stream: true });
|
||||
}
|
||||
} finally {
|
||||
reader.releaseLock();
|
||||
}
|
||||
}
|
||||
|
||||
function resolveHomePath(value) {
|
||||
if (value === "~") {
|
||||
return process.env.HOME;
|
||||
@@ -806,16 +851,31 @@ async function assertClawHubPreflight() {
|
||||
const body = await withTimeout(
|
||||
`ClawHub package preflight response for ${packageName}`,
|
||||
CLAWHUB_PREFLIGHT_TIMEOUT_MS,
|
||||
() => response.text().catch(() => ""),
|
||||
() =>
|
||||
readBoundedResponseText(
|
||||
response,
|
||||
`ClawHub package preflight response for ${packageName}`,
|
||||
CLAWHUB_PREFLIGHT_BODY_MAX_BYTES,
|
||||
),
|
||||
);
|
||||
throw new Error(
|
||||
`ClawHub package preflight failed for ${packageName}: ${response.status} ${body}`,
|
||||
);
|
||||
}
|
||||
const detail = await withTimeout(
|
||||
const rawDetail = await withTimeout(
|
||||
`ClawHub package preflight response for ${packageName}`,
|
||||
CLAWHUB_PREFLIGHT_TIMEOUT_MS,
|
||||
() => response.json(),
|
||||
() =>
|
||||
readBoundedResponseText(
|
||||
response,
|
||||
`ClawHub package preflight response for ${packageName}`,
|
||||
CLAWHUB_PREFLIGHT_BODY_MAX_BYTES,
|
||||
),
|
||||
);
|
||||
const detail = await withTimeout(
|
||||
`ClawHub package preflight JSON for ${packageName}`,
|
||||
CLAWHUB_PREFLIGHT_TIMEOUT_MS,
|
||||
() => JSON.parse(rawDetail),
|
||||
);
|
||||
const family = detail.package?.family;
|
||||
if (family !== "code-plugin" && family !== "bundle-plugin") {
|
||||
|
||||
@@ -236,4 +236,38 @@ describe("plugins Docker assertions", () => {
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it("bounds ClawHub package metadata response bodies", async () => {
|
||||
const server = createServer((_request, response) => {
|
||||
response.writeHead(500, { "content-type": "text/plain" });
|
||||
response.end("x".repeat(128));
|
||||
});
|
||||
await new Promise<void>((resolve) => {
|
||||
server.listen(0, "127.0.0.1", resolve);
|
||||
});
|
||||
|
||||
try {
|
||||
const address = server.address();
|
||||
if (!address || typeof address === "string") {
|
||||
throw new Error("expected TCP server address");
|
||||
}
|
||||
const result = await runAssertionAsync(["clawhub-preflight"], {
|
||||
CLAWHUB_PLUGIN_ID: "openclaw-kitchen-sink-fixture",
|
||||
CLAWHUB_PLUGIN_SPEC: "clawhub:@openclaw/kitchen-sink",
|
||||
OPENCLAW_CLAWHUB_URL: `http://127.0.0.1:${address.port}`,
|
||||
OPENCLAW_PLUGINS_E2E_CLAWHUB_PREFLIGHT_BODY_MAX_BYTES: "16",
|
||||
OPENCLAW_PLUGINS_E2E_CLAWHUB_PREFLIGHT_TIMEOUT_MS: "1000",
|
||||
});
|
||||
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(result.stderr).toContain(
|
||||
"ClawHub package preflight response for @openclaw/kitchen-sink response body exceeded 16 bytes",
|
||||
);
|
||||
expect(result.stderr).not.toContain("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
|
||||
} finally {
|
||||
await new Promise<void>((resolve) => {
|
||||
server.close(() => resolve());
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user