From 9ee800e81dc3f0aa92ff6723e5ad3888550ed961 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 23 Apr 2026 18:22:16 +0100 Subject: [PATCH] test: share security audit temp fixtures --- src/security/audit-config-symlink.test.ts | 20 ++++---------- src/security/audit-filesystem-windows.test.ts | 22 +++++---------- .../audit-workspace-skill-escape.test.ts | 26 ++++++------------ src/security/test-temp-cases.ts | 27 +++++++++++++++++++ 4 files changed, 46 insertions(+), 49 deletions(-) create mode 100644 src/security/test-temp-cases.ts diff --git a/src/security/audit-config-symlink.test.ts b/src/security/audit-config-symlink.test.ts index 3aef6930feb..e922b87ce85 100644 --- a/src/security/audit-config-symlink.test.ts +++ b/src/security/audit-config-symlink.test.ts @@ -1,38 +1,28 @@ import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { collectFilesystemFindings } from "./audit.js"; +import { AsyncTempCaseFactory } from "./test-temp-cases.js"; const isWindows = process.platform === "win32"; describe("security audit config symlink findings", () => { - let fixtureRoot = ""; - let caseId = 0; + const tempCases = new AsyncTempCaseFactory("openclaw-security-audit-config-"); beforeAll(async () => { - fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-security-audit-config-")); + await tempCases.setup(); }); afterAll(async () => { - if (!fixtureRoot) { - return; - } - await fs.rm(fixtureRoot, { recursive: true, force: true }).catch(() => undefined); + await tempCases.cleanup(); }); - const makeTmpDir = async (label: string) => { - const dir = path.join(fixtureRoot, `case-${caseId++}-${label}`); - await fs.mkdir(dir, { recursive: true }); - return dir; - }; - it("uses symlink target permissions for config checks", async () => { if (isWindows) { return; } - const tmp = await makeTmpDir("config-symlink"); + const tmp = await tempCases.makeTmpDir("config-symlink"); const stateDir = path.join(tmp, "state"); await fs.mkdir(stateDir, { recursive: true, mode: 0o700 }); diff --git a/src/security/audit-filesystem-windows.test.ts b/src/security/audit-filesystem-windows.test.ts index 8790b01003b..8687a0e6c21 100644 --- a/src/security/audit-filesystem-windows.test.ts +++ b/src/security/audit-filesystem-windows.test.ts @@ -1,8 +1,8 @@ import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { collectFilesystemFindings } from "./audit.js"; +import { AsyncTempCaseFactory } from "./test-temp-cases.js"; const windowsAuditEnv = { USERNAME: "Tester", @@ -10,30 +10,20 @@ const windowsAuditEnv = { }; describe("security audit filesystem Windows findings", () => { - let fixtureRoot = ""; - let caseId = 0; + const tempCases = new AsyncTempCaseFactory("openclaw-security-audit-win-"); beforeAll(async () => { - fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-security-audit-win-")); + await tempCases.setup(); }); afterAll(async () => { - if (!fixtureRoot) { - return; - } - await fs.rm(fixtureRoot, { recursive: true, force: true }).catch(() => undefined); + await tempCases.cleanup(); }); - const makeTmpDir = async (label: string) => { - const dir = path.join(fixtureRoot, `case-${caseId++}-${label}`); - await fs.mkdir(dir, { recursive: true }); - return dir; - }; - it("evaluates Windows ACL-derived filesystem findings", async () => { await Promise.all([ (async () => { - const tmp = await makeTmpDir("win"); + const tmp = await tempCases.makeTmpDir("win"); const stateDir = path.join(tmp, "state"); await fs.mkdir(stateDir, { recursive: true }); const configPath = path.join(stateDir, "openclaw.json"); @@ -64,7 +54,7 @@ describe("security audit filesystem Windows findings", () => { } })(), (async () => { - const tmp = await makeTmpDir("win-open"); + const tmp = await tempCases.makeTmpDir("win-open"); const stateDir = path.join(tmp, "state"); await fs.mkdir(stateDir, { recursive: true }); const configPath = path.join(stateDir, "openclaw.json"); diff --git a/src/security/audit-workspace-skill-escape.test.ts b/src/security/audit-workspace-skill-escape.test.ts index 82fbdf85614..b47130231de 100644 --- a/src/security/audit-workspace-skill-escape.test.ts +++ b/src/security/audit-workspace-skill-escape.test.ts @@ -1,38 +1,28 @@ import fs from "node:fs/promises"; -import os from "node:os"; import path from "node:path"; import { afterAll, beforeAll, describe, expect, it, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { collectWorkspaceSkillSymlinkEscapeFindings } from "./audit-extra.async.js"; +import { AsyncTempCaseFactory } from "./test-temp-cases.js"; const isWindows = process.platform === "win32"; describe("security audit workspace skill path escape findings", () => { - let fixtureRoot = ""; - let caseId = 0; + const tempCases = new AsyncTempCaseFactory("openclaw-security-audit-workspace-"); beforeAll(async () => { - fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-security-audit-workspace-")); + await tempCases.setup(); }); afterAll(async () => { - if (!fixtureRoot) { - return; - } - await fs.rm(fixtureRoot, { recursive: true, force: true }).catch(() => undefined); + await tempCases.cleanup(); }); - const makeTmpDir = async (label: string) => { - const dir = path.join(fixtureRoot, `case-${caseId++}-${label}`); - await fs.mkdir(dir, { recursive: true }); - return dir; - }; - it("evaluates workspace skill path escape findings", async () => { const runs = [ !isWindows ? (async () => { - const tmp = await makeTmpDir("workspace-skill-symlink-escape"); + const tmp = await tempCases.makeTmpDir("workspace-skill-symlink-escape"); const workspaceDir = path.join(tmp, "workspace"); const outsideDir = path.join(tmp, "outside"); await fs.mkdir(path.join(workspaceDir, "skills", "leak"), { recursive: true }); @@ -54,7 +44,7 @@ describe("security audit workspace skill path escape findings", () => { })() : Promise.resolve(), (async () => { - const tmp = await makeTmpDir("workspace-skill-in-root"); + const tmp = await tempCases.makeTmpDir("workspace-skill-in-root"); const workspaceDir = path.join(tmp, "workspace"); await fs.mkdir(path.join(workspaceDir, "skills", "safe"), { recursive: true }); await fs.writeFile( @@ -75,7 +65,7 @@ describe("security audit workspace skill path escape findings", () => { }); it("treats an unresolvable realpath (timeout/error simulation) as a potential symlink escape", async () => { - const tmp = await makeTmpDir("workspace-skill-realpath-unresolvable"); + const tmp = await tempCases.makeTmpDir("workspace-skill-realpath-unresolvable"); const workspaceDir = path.join(tmp, "workspace"); const skillsDir = path.join(workspaceDir, "skills", "suspect-skill"); await fs.mkdir(skillsDir, { recursive: true }); @@ -112,7 +102,7 @@ describe("security audit workspace skill path escape findings", () => { }); it("surfaces scan_truncated finding when BFS visit cap is hit", async () => { - const tmp = await makeTmpDir("workspace-skill-bfs-truncated"); + const tmp = await tempCases.makeTmpDir("workspace-skill-bfs-truncated"); const workspaceDir = path.join(tmp, "workspace"); const skillsRoot = path.join(workspaceDir, "skills"); await fs.mkdir(skillsRoot, { recursive: true }); diff --git a/src/security/test-temp-cases.ts b/src/security/test-temp-cases.ts new file mode 100644 index 00000000000..e4d813b5d73 --- /dev/null +++ b/src/security/test-temp-cases.ts @@ -0,0 +1,27 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; + +export class AsyncTempCaseFactory { + private caseId = 0; + private fixtureRoot = ""; + + constructor(private readonly prefix: string) {} + + async setup() { + this.fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), this.prefix)); + } + + async cleanup() { + if (!this.fixtureRoot) { + return; + } + await fs.rm(this.fixtureRoot, { recursive: true, force: true }).catch(() => undefined); + } + + async makeTmpDir(label: string) { + const dir = path.join(this.fixtureRoot, `case-${this.caseId++}-${label}`); + await fs.mkdir(dir, { recursive: true }); + return dir; + } +}