feat: add exec approvals tooling and service status
This commit is contained in:
108
src/infra/exec-approvals.test.ts
Normal file
108
src/infra/exec-approvals.test.ts
Normal file
@@ -0,0 +1,108 @@
|
||||
import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
matchAllowlist,
|
||||
maxAsk,
|
||||
minSecurity,
|
||||
resolveCommandResolution,
|
||||
type ExecAllowlistEntry,
|
||||
} from "./exec-approvals.js";
|
||||
|
||||
function makeTempDir() {
|
||||
return fs.mkdtempSync(path.join(os.tmpdir(), "clawdbot-exec-approvals-"));
|
||||
}
|
||||
|
||||
describe("exec approvals allowlist matching", () => {
|
||||
it("matches by executable name (case-insensitive)", () => {
|
||||
const resolution = {
|
||||
rawExecutable: "rg",
|
||||
resolvedPath: "/opt/homebrew/bin/rg",
|
||||
executableName: "rg",
|
||||
};
|
||||
const entries: ExecAllowlistEntry[] = [{ pattern: "RG" }];
|
||||
const match = matchAllowlist(entries, resolution);
|
||||
expect(match?.pattern).toBe("RG");
|
||||
});
|
||||
|
||||
it("matches by resolved path with **", () => {
|
||||
const resolution = {
|
||||
rawExecutable: "rg",
|
||||
resolvedPath: "/opt/homebrew/bin/rg",
|
||||
executableName: "rg",
|
||||
};
|
||||
const entries: ExecAllowlistEntry[] = [{ pattern: "/opt/**/rg" }];
|
||||
const match = matchAllowlist(entries, resolution);
|
||||
expect(match?.pattern).toBe("/opt/**/rg");
|
||||
});
|
||||
|
||||
it("does not let * cross path separators", () => {
|
||||
const resolution = {
|
||||
rawExecutable: "rg",
|
||||
resolvedPath: "/opt/homebrew/bin/rg",
|
||||
executableName: "rg",
|
||||
};
|
||||
const entries: ExecAllowlistEntry[] = [{ pattern: "/opt/*/rg" }];
|
||||
const match = matchAllowlist(entries, resolution);
|
||||
expect(match).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to raw executable when no resolved path", () => {
|
||||
const resolution = {
|
||||
rawExecutable: "bin/rg",
|
||||
resolvedPath: undefined,
|
||||
executableName: "rg",
|
||||
};
|
||||
const entries: ExecAllowlistEntry[] = [{ pattern: "bin/rg" }];
|
||||
const match = matchAllowlist(entries, resolution);
|
||||
expect(match?.pattern).toBe("bin/rg");
|
||||
});
|
||||
});
|
||||
|
||||
describe("exec approvals command resolution", () => {
|
||||
it("resolves PATH executables", () => {
|
||||
const dir = makeTempDir();
|
||||
const binDir = path.join(dir, "bin");
|
||||
fs.mkdirSync(binDir, { recursive: true });
|
||||
const exe = path.join(binDir, "rg");
|
||||
fs.writeFileSync(exe, "");
|
||||
const res = resolveCommandResolution("rg -n foo", undefined, { PATH: binDir });
|
||||
expect(res?.resolvedPath).toBe(exe);
|
||||
expect(res?.executableName).toBe("rg");
|
||||
});
|
||||
|
||||
it("resolves relative paths against cwd", () => {
|
||||
const dir = makeTempDir();
|
||||
const cwd = path.join(dir, "project");
|
||||
const script = path.join(cwd, "scripts", "run.sh");
|
||||
fs.mkdirSync(path.dirname(script), { recursive: true });
|
||||
fs.writeFileSync(script, "");
|
||||
const res = resolveCommandResolution("./scripts/run.sh --flag", cwd, undefined);
|
||||
expect(res?.resolvedPath).toBe(script);
|
||||
});
|
||||
|
||||
it("parses quoted executables", () => {
|
||||
const dir = makeTempDir();
|
||||
const cwd = path.join(dir, "project");
|
||||
const script = path.join(cwd, "bin", "tool");
|
||||
fs.mkdirSync(path.dirname(script), { recursive: true });
|
||||
fs.writeFileSync(script, "");
|
||||
const res = resolveCommandResolution("\"./bin/tool\" --version", cwd, undefined);
|
||||
expect(res?.resolvedPath).toBe(script);
|
||||
});
|
||||
});
|
||||
|
||||
describe("exec approvals policy helpers", () => {
|
||||
it("minSecurity returns the more restrictive value", () => {
|
||||
expect(minSecurity("deny", "full")).toBe("deny");
|
||||
expect(minSecurity("allowlist", "full")).toBe("allowlist");
|
||||
});
|
||||
|
||||
it("maxAsk returns the more aggressive ask mode", () => {
|
||||
expect(maxAsk("off", "always")).toBe("always");
|
||||
expect(maxAsk("on-miss", "off")).toBe("on-miss");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user