mirror of
https://github.com/prompt-security/clawsec.git
synced 2026-06-16 15:01:22 +03:00
caad6f698c
* chore(skills): harden openclaw skill metadata * fix(openclaw-audit-watchdog): add dated release note heading * chore(skills): normalize openclaw naming * fix(soul-guardian): preserve legacy launchd state dir * fix(soul-guardian): clean up legacy launchd labels
184 lines
5.3 KiB
JavaScript
184 lines
5.3 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { spawn } from "node:child_process";
|
|
import { fileURLToPath } from "node:url";
|
|
import { createTempDir, pass, fail, report, exitWithResults } from "./lib/test_harness.mjs";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const NODE_BIN = process.execPath;
|
|
const SETUP_CRON_SCRIPT = path.resolve(__dirname, "..", "scripts", "setup_advisory_cron.mjs");
|
|
const SETUP_HOOK_SCRIPT = path.resolve(__dirname, "..", "scripts", "setup_advisory_hook.mjs");
|
|
|
|
async function writeExecutable(filePath, content) {
|
|
await fs.writeFile(filePath, content, { encoding: "utf8", mode: 0o755 });
|
|
}
|
|
|
|
async function createOpenClawFixture() {
|
|
const tmp = await createTempDir();
|
|
const binDir = path.join(tmp.path, "bin");
|
|
const capturePath = path.join(tmp.path, "openclaw-calls.json");
|
|
|
|
await fs.mkdir(binDir, { recursive: true });
|
|
await writeExecutable(
|
|
path.join(binDir, "openclaw"),
|
|
`#!/usr/bin/env node
|
|
import fs from "node:fs";
|
|
|
|
const capturePath = process.env.OPENCLAW_CAPTURE_PATH;
|
|
const args = process.argv.slice(2);
|
|
let entries = [];
|
|
if (capturePath && fs.existsSync(capturePath)) {
|
|
entries = JSON.parse(fs.readFileSync(capturePath, "utf8"));
|
|
}
|
|
entries.push(args);
|
|
if (capturePath) {
|
|
fs.writeFileSync(capturePath, JSON.stringify(entries), "utf8");
|
|
}
|
|
|
|
if (args[0] === "--version") {
|
|
process.stdout.write("openclaw test\\n");
|
|
process.exit(0);
|
|
}
|
|
if (args[0] === "cron" && args[1] === "list") {
|
|
process.stdout.write(JSON.stringify({ jobs: [] }) + "\\n");
|
|
process.exit(0);
|
|
}
|
|
if (args[0] === "cron" && args[1] === "add") {
|
|
process.stdout.write(JSON.stringify({ id: "cron-123" }) + "\\n");
|
|
process.exit(0);
|
|
}
|
|
if (args[0] === "cron" && args[1] === "edit") {
|
|
process.stdout.write("{}\\n");
|
|
process.exit(0);
|
|
}
|
|
if (args[0] === "hooks" && args[1] === "enable") {
|
|
process.stdout.write("enabled\\n");
|
|
process.exit(0);
|
|
}
|
|
|
|
process.stderr.write("unexpected args: " + JSON.stringify(args) + "\\n");
|
|
process.exit(1);
|
|
`,
|
|
);
|
|
|
|
return { tmp, binDir, capturePath };
|
|
}
|
|
|
|
async function runNodeScript(scriptPath, env) {
|
|
return await new Promise((resolve) => {
|
|
const proc = spawn(NODE_BIN, [scriptPath], {
|
|
env,
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
|
|
let stdout = "";
|
|
let stderr = "";
|
|
|
|
proc.stdout.on("data", (data) => {
|
|
stdout += data.toString();
|
|
});
|
|
|
|
proc.stderr.on("data", (data) => {
|
|
stderr += data.toString();
|
|
});
|
|
|
|
proc.on("close", async (code) => {
|
|
resolve({ code, stdout, stderr });
|
|
});
|
|
});
|
|
}
|
|
|
|
async function testAdvisoryCronPreflight() {
|
|
const testName = "setup_advisory_cron: prints preflight review before creating unattended cron";
|
|
const fixture = await createOpenClawFixture();
|
|
|
|
try {
|
|
const result = await runNodeScript(SETUP_CRON_SCRIPT, {
|
|
...process.env,
|
|
PATH: `${fixture.binDir}:${process.env.PATH || ""}`,
|
|
OPENCLAW_CAPTURE_PATH: fixture.capturePath,
|
|
CLAWSEC_ADVISORY_CRON_EVERY: "6h",
|
|
});
|
|
|
|
if (result.code !== 0) {
|
|
fail(testName, `script failed: ${result.stderr}`);
|
|
return;
|
|
}
|
|
|
|
const captures = JSON.parse(await fs.readFile(fixture.capturePath, "utf8"));
|
|
const sawAdd = captures.some((args) => args[0] === "cron" && args[1] === "add");
|
|
|
|
if (
|
|
sawAdd &&
|
|
result.stdout.includes("Preflight review:") &&
|
|
result.stdout.includes("unattended openclaw cron job") &&
|
|
result.stdout.includes("Schedule: every 6h") &&
|
|
result.stdout.includes("request explicit approval before any removal")
|
|
) {
|
|
pass(testName);
|
|
} else {
|
|
fail(testName, `missing preflight details: ${result.stdout}`);
|
|
}
|
|
} finally {
|
|
await fixture.tmp.cleanup();
|
|
}
|
|
}
|
|
|
|
async function testAdvisoryHookPreflight() {
|
|
const testName = "setup_advisory_hook: prints preflight review before installing persistent hook";
|
|
const fixture = await createOpenClawFixture();
|
|
const homeDir = path.join(fixture.tmp.path, "home");
|
|
|
|
try {
|
|
await fs.mkdir(homeDir, { recursive: true });
|
|
|
|
const result = await runNodeScript(SETUP_HOOK_SCRIPT, {
|
|
...process.env,
|
|
HOME: homeDir,
|
|
PATH: `${fixture.binDir}:${process.env.PATH || ""}`,
|
|
OPENCLAW_CAPTURE_PATH: fixture.capturePath,
|
|
});
|
|
|
|
if (result.code !== 0) {
|
|
fail(testName, `script failed: ${result.stderr}`);
|
|
return;
|
|
}
|
|
|
|
const installedHook = path.join(homeDir, ".openclaw", "hooks", "clawsec-advisory-guardian", "HOOK.md");
|
|
const captures = JSON.parse(await fs.readFile(fixture.capturePath, "utf8"));
|
|
const sawEnable = captures.some((args) => args[0] === "hooks" && args[1] === "enable");
|
|
|
|
await fs.access(installedHook);
|
|
|
|
if (
|
|
sawEnable &&
|
|
result.stdout.includes("Preflight review:") &&
|
|
result.stdout.includes("persistent OpenClaw hook") &&
|
|
result.stdout.includes("fetches signed advisory feed data") &&
|
|
result.stdout.includes("Restart your OpenClaw gateway process")
|
|
) {
|
|
pass(testName);
|
|
} else {
|
|
fail(testName, `missing hook preflight details: ${result.stdout}`);
|
|
}
|
|
} catch (error) {
|
|
fail(testName, error);
|
|
} finally {
|
|
await fixture.tmp.cleanup();
|
|
}
|
|
}
|
|
|
|
async function runAllTests() {
|
|
await testAdvisoryCronPreflight();
|
|
await testAdvisoryHookPreflight();
|
|
report();
|
|
exitWithResults();
|
|
}
|
|
|
|
runAllTests().catch((err) => {
|
|
console.error("Test runner failed:", err);
|
|
process.exit(1);
|
|
});
|