diff --git a/scripts/e2e/lib/release-user-journey/assertions.mjs b/scripts/e2e/lib/release-user-journey/assertions.mjs index cdf249ba1a4..709c5f98547 100644 --- a/scripts/e2e/lib/release-user-journey/assertions.mjs +++ b/scripts/e2e/lib/release-user-journey/assertions.mjs @@ -14,6 +14,7 @@ import { readTextFileTail } from "../text-file-utils.mjs"; 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 clickClackHttpTimeoutMs() { return readPositiveInt( @@ -31,8 +32,30 @@ function clickClackHttpBodyMaxBytes() { ); } -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 readPositiveInt(raw, fallback, label) { diff --git a/test/scripts/release-user-journey-assertions.test.ts b/test/scripts/release-user-journey-assertions.test.ts index a2036dc21e5..88c81c35052 100644 --- a/test/scripts/release-user-journey-assertions.test.ts +++ b/test/scripts/release-user-journey-assertions.test.ts @@ -137,6 +137,31 @@ describe("release user journey assertions", () => { } }); + it("rejects oversized JSON artifacts before parsing release user journey config", () => { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-")); + const home = path.join(root, "home"); + const configPath = path.join(home, ".openclaw", "openclaw.json"); + + try { + mkdirSync(path.dirname(configPath), { recursive: true }); + writeFileSync( + configPath, + `DO_NOT_DUMP_OLD_JSON${"x".repeat(2 * 1024 * 1024)}\nrecent json tail`, + "utf8", + ); + + const result = runAssertion(home, ["configure-mock-model", "18080"]); + + 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("fails when uninstall leaves the managed plugin directory behind", () => { const root = mkdtempSync(path.join(tmpdir(), "openclaw-release-user-assertions-")); const home = path.join(root, "home");