From 0a2cad7e688e2a7a38f723720f97bb9ff50f9190 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 7 Jun 2026 09:24:31 +0200 Subject: [PATCH] fix(e2e): bound live plugin transcript scans --- .../e2e/lib/live-plugin-tool/assertions.mjs | 52 ++++++++++++------- .../live-plugin-tool-assertions.test.ts | 24 +++++++++ 2 files changed, 57 insertions(+), 19 deletions(-) diff --git a/scripts/e2e/lib/live-plugin-tool/assertions.mjs b/scripts/e2e/lib/live-plugin-tool/assertions.mjs index 1a160d9a849b..9e83fdc433ab 100644 --- a/scripts/e2e/lib/live-plugin-tool/assertions.mjs +++ b/scripts/e2e/lib/live-plugin-tool/assertions.mjs @@ -32,6 +32,10 @@ const AGENT_OUTPUT_MAX_BYTES = readPositiveIntEnv( 1024 * 1024, ); const SESSION_FILE_LIST_LIMIT = 20; +const SESSION_SCAN_MAX_ENTRIES = readPositiveIntEnv( + "OPENCLAW_LIVE_PLUGIN_TOOL_SESSION_SCAN_MAX_ENTRIES", + 50_000, +); function requireEnv(name) { const value = process.env[name]; @@ -112,28 +116,38 @@ function scanSessionTranscripts(sessionsDir, needles) { } const pendingDirs = [sessionsDir]; + let scannedEntries = 0; while (pendingDirs.length > 0) { const dir = pendingDirs.pop(); - const entries = fs - .readdirSync(dir, { withFileTypes: true }) - .toSorted((left, right) => left.name.localeCompare(right.name)); - for (const entry of entries) { - const entryPath = path.join(dir, entry.name); - if (entry.isDirectory()) { - pendingDirs.push(entryPath); - continue; - } - if (!entry.isFile() || !entry.name.endsWith(".jsonl")) { - continue; - } - filesChecked += 1; - if (checkedFiles.length < SESSION_FILE_LIST_LIMIT) { - checkedFiles.push(path.relative(sessionsDir, entryPath)); - } - if (scanFileForNeedles(entryPath, needles).size === 0) { - pendingNeedles.clear(); - return { checkedFiles, filesChecked, missingDir: false, pendingNeedles }; + const handle = fs.opendirSync(dir); + try { + let entry; + while ((entry = handle.readSync()) !== null) { + scannedEntries += 1; + if (scannedEntries > SESSION_SCAN_MAX_ENTRIES) { + throw new Error( + `session transcript scan exceeded ${SESSION_SCAN_MAX_ENTRIES} filesystem entries`, + ); + } + const entryPath = path.join(dir, entry.name); + if (entry.isDirectory()) { + pendingDirs.push(entryPath); + continue; + } + if (!entry.isFile() || !entry.name.endsWith(".jsonl")) { + continue; + } + filesChecked += 1; + if (checkedFiles.length < SESSION_FILE_LIST_LIMIT) { + checkedFiles.push(path.relative(sessionsDir, entryPath)); + } + if (scanFileForNeedles(entryPath, needles).size === 0) { + pendingNeedles.clear(); + return { checkedFiles, filesChecked, missingDir: false, pendingNeedles }; + } } + } finally { + handle.closeSync(); } } return { checkedFiles, filesChecked, missingDir: false, pendingNeedles }; diff --git a/test/scripts/live-plugin-tool-assertions.test.ts b/test/scripts/live-plugin-tool-assertions.test.ts index d0759880af4f..e21bfb4a06fe 100644 --- a/test/scripts/live-plugin-tool-assertions.test.ts +++ b/test/scripts/live-plugin-tool-assertions.test.ts @@ -126,6 +126,30 @@ describe("live plugin tool assertions", () => { } }); + it("bounds session transcript traversal before scanning unbounded trees", () => { + const root = mkdtempSync(path.join(tmpdir(), "openclaw-live-plugin-tool-")); + const sessionsDir = path.join(root, "state", "agents", "main", "sessions"); + + try { + writeJson(path.join(root, "agent.json"), { + payloads: [{ text: "live-plugin-slug" }], + }); + mkdirSync(sessionsDir, { recursive: true }); + for (let index = 0; index < 4; index += 1) { + writeFileSync(path.join(sessionsDir, `noise-${index}.jsonl`), "noise\n", "utf8"); + } + + const result = runAssertion(root, { + OPENCLAW_LIVE_PLUGIN_TOOL_SESSION_SCAN_MAX_ENTRIES: "2", + }); + + expect(result.status).not.toBe(0); + expect(result.stderr).toContain("session transcript scan exceeded 2 filesystem entries"); + } finally { + rmSync(root, { force: true, recursive: true }); + } + }); + it("rejects markers that only appear in error payload text", () => { const root = mkdtempSync(path.join(tmpdir(), "openclaw-live-plugin-tool-")); const sessionsDir = path.join(root, "state", "agents", "main", "sessions");