fix(e2e): bound live plugin transcript scans

This commit is contained in:
Vincent Koc
2026-06-07 09:24:31 +02:00
parent 0bf487e4cb
commit 0a2cad7e68
2 changed files with 57 additions and 19 deletions

View File

@@ -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 };

View File

@@ -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");