fix(e2e): bound kitchen sink log traversal

This commit is contained in:
Vincent Koc
2026-06-07 07:25:46 +02:00
parent 251bd61e22
commit 0b0893aa21
2 changed files with 63 additions and 1 deletions

View File

@@ -9,6 +9,7 @@ const scratchRoot = process.env.KITCHEN_SINK_TMP_DIR || os.tmpdir();
const LOG_SCAN_CHUNK_BYTES = 64 * 1024;
const LOG_SCAN_FINDING_CONTEXT_CHARS = 2048;
const LOG_SCAN_MAX_ENTRIES = readPositiveIntEnv("KITCHEN_SINK_LOG_SCAN_MAX_ENTRIES", 20_000);
const LOG_SCAN_MAX_FILES = 5000;
const LOG_SCAN_MAX_FINDINGS = 100;
const LOG_SCAN_MAX_LINE_CHARS = 16 * 1024;
@@ -18,6 +19,22 @@ const readJson = (file) => JSON.parse(fs.readFileSync(file, "utf8"));
const scratchFile = (name) => path.join(scratchRoot, name);
const normalizedPath = (filePath) => filePath.replaceAll("\\", "/");
function readPositiveIntEnv(name, fallback) {
const raw = process.env[name];
if (raw === undefined || raw === "") {
return fallback;
}
const text = raw.trim();
if (!/^\d+$/u.test(text)) {
throw new Error(`${name} must be a positive integer; got: ${raw}`);
}
const parsed = Number(text);
if (!Number.isSafeInteger(parsed) || parsed <= 0) {
throw new Error(`${name} must be a positive integer; got: ${raw}`);
}
return parsed;
}
function resolveHomePath(value) {
if (value === "~") {
return process.env.HOME;
@@ -115,6 +132,7 @@ function shouldScanLogFile(entry) {
function scanLogFiles(roots, onFile) {
let scannedFiles = 0;
let visitedEntries = 0;
for (const root of roots) {
const pending = [root];
while (pending.length > 0) {
@@ -122,6 +140,12 @@ function scanLogFiles(roots, onFile) {
if (!entry || !fs.existsSync(entry)) {
continue;
}
visitedEntries += 1;
if (visitedEntries > LOG_SCAN_MAX_ENTRIES) {
throw new Error(
`kitchen-sink log scan exceeded ${LOG_SCAN_MAX_ENTRIES} filesystem entries`,
);
}
const stat = fs.lstatSync(entry);
if (stat.isSymbolicLink()) {
continue;

View File

@@ -193,11 +193,20 @@ function runAssertClawhubInstalled({
}
}
function runScanLogs({ home, scratchRoot }: { home: string; scratchRoot: string }) {
function runScanLogs({
env = {},
home,
scratchRoot,
}: {
env?: NodeJS.ProcessEnv;
home: string;
scratchRoot: string;
}) {
return spawnSync(process.execPath, [ASSERTIONS_SCRIPT, "scan-logs"], {
encoding: "utf8",
env: {
...process.env,
...env,
HOME: home,
KITCHEN_SINK_TMP_DIR: scratchRoot,
},
@@ -291,6 +300,35 @@ describe("kitchen-sink plugin assertions", () => {
}
});
it("bounds irrelevant OpenClaw home traversal during log scans", () => {
const parent = mkdtempSync(path.join(tmpdir(), "openclaw-kitchen-sink-scan-"));
const home = path.join(parent, "home");
const scratchRoot = path.join(parent, "scratch");
try {
mkdirSync(path.join(home, ".openclaw"), { recursive: true });
mkdirSync(scratchRoot, { recursive: true });
writeFileSync(path.join(scratchRoot, "scenario.log"), "0 errors\n");
for (let index = 0; index < 20; index += 1) {
const dir = path.join(home, ".openclaw", `cache-${index}`);
mkdirSync(dir, { recursive: true });
writeFileSync(path.join(dir, "state.txt"), "not a log\n");
}
const result = runScanLogs({
env: { KITCHEN_SINK_LOG_SCAN_MAX_ENTRIES: "8" },
home,
scratchRoot,
});
expect(result.status).not.toBe(0);
expect(`${result.stdout}\n${result.stderr}`).toContain(
"kitchen-sink log scan exceeded 8 filesystem entries",
);
} finally {
rmSync(parent, { force: true, recursive: true });
}
});
it("does not allow dirty error lines just because they mention zero errors", () => {
const parent = mkdtempSync(path.join(tmpdir(), "openclaw-kitchen-sink-scan-"));
const home = path.join(parent, "home");