migrate security parser to sh-syntax and async validation
This commit is contained in:
@@ -10,8 +10,10 @@ import {
|
||||
parseShellScript,
|
||||
} from "../src/security/index.js";
|
||||
|
||||
test("shell parser extracts Command and Word nodes across chained expressions", () => {
|
||||
const parsed = parseShellScript("FOO=bar git status && npm test | cat > logs/output.txt");
|
||||
test("shell parser extracts commands across chained expressions", async () => {
|
||||
const parsed = await parseShellScript(
|
||||
"FOO=bar git status && npm test | cat > logs/output.txt",
|
||||
);
|
||||
|
||||
assert.equal(parsed.commandCount, 3);
|
||||
assert.deepEqual(
|
||||
@@ -33,11 +35,12 @@ test("rules engine enforces binary allowlist, tool policy, and path boundaries",
|
||||
const worktreeRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-security-worktree-"));
|
||||
const stateRoot = await mkdtemp(resolve(tmpdir(), "ai-ops-security-state-"));
|
||||
const projectContextPath = resolve(stateRoot, "project-context.json");
|
||||
const protectedFilePath = resolve(worktreeRoot, ".ai_ops", "project-context.json");
|
||||
|
||||
const rules = new SecurityRulesEngine({
|
||||
allowedBinaries: ["git", "npm", "cat"],
|
||||
worktreeRoot,
|
||||
protectedPaths: [stateRoot, projectContextPath],
|
||||
protectedPaths: [stateRoot, projectContextPath, protectedFilePath],
|
||||
requireCwdWithinWorktree: true,
|
||||
rejectRelativePathTraversal: true,
|
||||
enforcePathBoundaryOnArguments: true,
|
||||
@@ -45,7 +48,7 @@ test("rules engine enforces binary allowlist, tool policy, and path boundaries",
|
||||
blockedEnvAssignments: [],
|
||||
});
|
||||
|
||||
const allowed = rules.validateShellCommand({
|
||||
const allowed = await rules.validateShellCommand({
|
||||
command: "git status && npm test | cat > logs/output.txt",
|
||||
cwd: worktreeRoot,
|
||||
toolClearance: {
|
||||
@@ -56,26 +59,55 @@ test("rules engine enforces binary allowlist, tool policy, and path boundaries",
|
||||
|
||||
assert.equal(allowed.parsed.commandCount, 3);
|
||||
|
||||
assert.throws(
|
||||
await assert.rejects(
|
||||
() =>
|
||||
rules.validateShellCommand({
|
||||
command: "cat ../secrets.txt",
|
||||
cwd: worktreeRoot,
|
||||
}),
|
||||
(error) =>
|
||||
error instanceof SecurityViolationError &&
|
||||
error.code === "PATH_TRAVERSAL_BLOCKED",
|
||||
(error: unknown) =>
|
||||
error instanceof SecurityViolationError && error.code === "PATH_TRAVERSAL_BLOCKED",
|
||||
);
|
||||
|
||||
assert.throws(
|
||||
await assert.rejects(
|
||||
() =>
|
||||
rules.validateShellCommand({
|
||||
command: "git status",
|
||||
cwd: stateRoot,
|
||||
}),
|
||||
(error) =>
|
||||
(error: unknown) =>
|
||||
error instanceof SecurityViolationError && error.code === "CWD_OUTSIDE_WORKTREE",
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
rules.validateShellCommand({
|
||||
command: "echo $(unauthorized_bin)",
|
||||
cwd: worktreeRoot,
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof SecurityViolationError && error.code === "SHELL_SUBSHELL_BLOCKED",
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
rules.validateShellCommand({
|
||||
command: "git status && unauthorized_bin",
|
||||
cwd: worktreeRoot,
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof SecurityViolationError && error.code === "BINARY_NOT_ALLOWED",
|
||||
);
|
||||
|
||||
await assert.rejects(
|
||||
() =>
|
||||
rules.validateShellCommand({
|
||||
command: `cat > ${protectedFilePath}`,
|
||||
cwd: worktreeRoot,
|
||||
}),
|
||||
(error: unknown) =>
|
||||
error instanceof SecurityViolationError &&
|
||||
error.code === "CWD_OUTSIDE_WORKTREE",
|
||||
error.code === "PATH_INSIDE_PROTECTED_PATH",
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user