diff --git a/scripts/e2e/lib/release-scenarios/assertions.mjs b/scripts/e2e/lib/release-scenarios/assertions.mjs index 4289d837ca9..9c1c8595f70 100644 --- a/scripts/e2e/lib/release-scenarios/assertions.mjs +++ b/scripts/e2e/lib/release-scenarios/assertions.mjs @@ -16,6 +16,7 @@ const command = process.argv[2]; const SCAN_CHUNK_BYTES = 64 * 1024; const SCAN_CARRY_CHARS = 256; const ERROR_DETAIL_TAIL_BYTES = 16 * 1024; +const JSON_ARTIFACT_MAX_BYTES = 2 * 1024 * 1024; function assert(condition, message) { if (!condition) { @@ -23,8 +24,30 @@ function assert(condition, message) { } } -function readJson(file) { - return JSON.parse(fs.readFileSync(file, "utf8")); +function readJson(file, maxBytes = JSON_ARTIFACT_MAX_BYTES) { + const stat = fs.statSync(file); + if (!stat.isFile()) { + throw new Error(`${file} is not a file`); + } + if (stat.size > maxBytes) { + throw new Error( + `JSON artifact exceeded ${maxBytes} bytes: ${file} (${stat.size} bytes). Tail: ${readTextFileTail( + file, + ERROR_DETAIL_TAIL_BYTES, + )}`, + ); + } + const text = fs.readFileSync(file, "utf8"); + const bytes = Buffer.byteLength(text, "utf8"); + if (bytes > maxBytes) { + throw new Error( + `JSON artifact exceeded ${maxBytes} bytes: ${file} (${bytes} bytes). Tail: ${readTextFileTail( + file, + ERROR_DETAIL_TAIL_BYTES, + )}`, + ); + } + return JSON.parse(text); } function fileContainsText(file, needle) { diff --git a/test/scripts/release-scenarios-assertions.test.ts b/test/scripts/release-scenarios-assertions.test.ts index 77a9c9425b9..fe18294b71c 100644 --- a/test/scripts/release-scenarios-assertions.test.ts +++ b/test/scripts/release-scenarios-assertions.test.ts @@ -129,6 +129,31 @@ describe("release scenario assertions", () => { } }); + it("rejects oversized JSON artifacts before parsing release scenario outputs", () => { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-")); + const outputPath = path.join(root, "describe.json"); + const requestLogPath = path.join(root, "requests.jsonl"); + + try { + writeFileSync( + outputPath, + `DO_NOT_DUMP_OLD_JSON${"x".repeat(2 * 1024 * 1024)}\nrecent json tail`, + "utf8", + ); + writeFileSync(requestLogPath, "/v1/responses\n", "utf8"); + + const result = runAssertion(["assert-image-describe", outputPath, requestLogPath]); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain("JSON artifact exceeded"); + expect(result.stderr).toContain("recent json tail"); + expect(result.stderr).not.toContain("DO_NOT_DUMP_OLD_JSON"); + expect(result.stderr.length).toBeLessThan(80 * 1024); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + it("scans large request logs for image generation requests", () => { const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-scenarios-")); const outputPath = path.join(root, "generate.json");