418 lines
11 KiB
TypeScript
418 lines
11 KiB
TypeScript
import { afterEach, expect, test } from "bun:test";
|
|
import { checkPermission } from "../permissions/checker";
|
|
import { cliPermissions } from "../permissions/cli";
|
|
import type { PermissionRules } from "../permissions/types";
|
|
|
|
// Clean up after each test
|
|
afterEach(() => {
|
|
cliPermissions.clear();
|
|
});
|
|
|
|
// ============================================================================
|
|
// CLI Permission Parsing Tests
|
|
// ============================================================================
|
|
|
|
test("Parse simple tool list", () => {
|
|
cliPermissions.setAllowedTools("Bash,Read,Write");
|
|
const tools = cliPermissions.getAllowedTools();
|
|
|
|
// Bash is normalized to Bash(:*), file tools get (**) wildcard
|
|
expect(tools).toEqual(["Bash(:*)", "Read(**)", "Write(**)"]);
|
|
});
|
|
|
|
test("Parse tool list with parameters", () => {
|
|
cliPermissions.setAllowedTools("Bash(npm install),Read(src/**)");
|
|
const tools = cliPermissions.getAllowedTools();
|
|
|
|
expect(tools).toEqual(["Bash(npm install)", "Read(src/**)"]);
|
|
});
|
|
|
|
test("Parse tool list with mixed formats", () => {
|
|
cliPermissions.setAllowedTools("Bash,Read(src/**),Write");
|
|
const tools = cliPermissions.getAllowedTools();
|
|
|
|
expect(tools).toEqual(["Bash(:*)", "Read(src/**)", "Write(**)"]);
|
|
});
|
|
|
|
test("Parse tool list with wildcards", () => {
|
|
cliPermissions.setAllowedTools("Bash(git diff:*),Bash(npm run test:*)");
|
|
const tools = cliPermissions.getAllowedTools();
|
|
|
|
expect(tools).toEqual(["Bash(git diff:*)", "Bash(npm run test:*)"]);
|
|
});
|
|
|
|
test("Handle empty tool list", () => {
|
|
cliPermissions.setAllowedTools("");
|
|
const tools = cliPermissions.getAllowedTools();
|
|
|
|
expect(tools).toEqual([]);
|
|
});
|
|
|
|
test("Handle whitespace in tool list", () => {
|
|
cliPermissions.setAllowedTools("Bash , Read , Write");
|
|
const tools = cliPermissions.getAllowedTools();
|
|
|
|
expect(tools).toEqual(["Bash(:*)", "Read(**)", "Write(**)"]);
|
|
});
|
|
|
|
// ============================================================================
|
|
// CLI allowedTools Override Tests
|
|
// ============================================================================
|
|
|
|
test("allowedTools overrides settings deny rules", () => {
|
|
cliPermissions.setAllowedTools("Bash");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const result = checkPermission(
|
|
"Bash",
|
|
{ command: "npm install" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
|
|
expect(result.decision).toBe("allow");
|
|
expect(result.matchedRule).toBe("Bash(:*) (CLI)");
|
|
expect(result.reason).toBe("Matched --allowedTools flag");
|
|
});
|
|
|
|
test("allowedTools with pattern matches specific command", () => {
|
|
cliPermissions.setAllowedTools("Bash(npm install)");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const result = checkPermission(
|
|
"Bash",
|
|
{ command: "npm install" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
|
|
expect(result.decision).toBe("allow");
|
|
expect(result.matchedRule).toBe("Bash(npm install) (CLI)");
|
|
});
|
|
|
|
test("allowedTools pattern does not match different command", () => {
|
|
cliPermissions.setAllowedTools("Bash(npm install)");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const result = checkPermission(
|
|
"Bash",
|
|
{ command: "rm -rf /" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
|
|
// Should not match, fall back to default behavior
|
|
expect(result.decision).toBe("ask");
|
|
expect(result.reason).toBe("Default behavior for tool");
|
|
});
|
|
|
|
test("allowedTools with wildcard prefix matches multiple commands", () => {
|
|
cliPermissions.setAllowedTools("Bash(npm run test:*)");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const result1 = checkPermission(
|
|
"Bash",
|
|
{ command: "npm run test:unit" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(result1.decision).toBe("allow");
|
|
|
|
const result2 = checkPermission(
|
|
"Bash",
|
|
{ command: "npm run test:integration" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(result2.decision).toBe("allow");
|
|
|
|
const result3 = checkPermission(
|
|
"Bash",
|
|
{ command: "npm run lint" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(result3.decision).toBe("ask"); // Should not match
|
|
});
|
|
|
|
test("allowedTools applies to multiple tools", () => {
|
|
cliPermissions.setAllowedTools("Bash,Read,Write");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const bashResult = checkPermission(
|
|
"Bash",
|
|
{ command: "ls" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(bashResult.decision).toBe("allow");
|
|
|
|
const readResult = checkPermission(
|
|
"Read",
|
|
{ file_path: "/etc/passwd" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(readResult.decision).toBe("allow");
|
|
|
|
const writeResult = checkPermission(
|
|
"Write",
|
|
{ file_path: "/tmp/test.txt" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(writeResult.decision).toBe("allow");
|
|
});
|
|
|
|
// ============================================================================
|
|
// CLI disallowedTools Override Tests
|
|
// ============================================================================
|
|
|
|
test("disallowedTools denies tool", () => {
|
|
cliPermissions.setDisallowedTools("WebFetch");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: ["WebFetch"],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const result = checkPermission(
|
|
"WebFetch",
|
|
{ url: "https://example.com" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
|
|
expect(result.decision).toBe("deny");
|
|
expect(result.matchedRule).toBe("WebFetch (CLI)");
|
|
expect(result.reason).toBe("Matched --disallowedTools flag");
|
|
});
|
|
|
|
test("disallowedTools with pattern denies specific command", () => {
|
|
cliPermissions.setDisallowedTools("Bash(curl:*)");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: ["Bash"],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const result = checkPermission(
|
|
"Bash",
|
|
{ command: "curl https://malicious.com" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
|
|
expect(result.decision).toBe("deny");
|
|
expect(result.matchedRule).toBe("Bash(curl:*) (CLI)");
|
|
});
|
|
|
|
test("disallowedTools overrides settings allow rules", () => {
|
|
cliPermissions.setDisallowedTools("Bash");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: ["Bash"],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const result = checkPermission(
|
|
"Bash",
|
|
{ command: "ls" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
|
|
expect(result.decision).toBe("deny");
|
|
expect(result.reason).toBe("Matched --disallowedTools flag");
|
|
});
|
|
|
|
test("disallowedTools does NOT override settings deny rules", () => {
|
|
cliPermissions.setAllowedTools("Bash");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: ["Bash(rm -rf:*)"],
|
|
ask: [],
|
|
};
|
|
|
|
const result = checkPermission(
|
|
"Bash",
|
|
{ command: "rm -rf /" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
|
|
// Settings deny should take precedence
|
|
expect(result.decision).toBe("deny");
|
|
expect(result.reason).toBe("Matched deny rule");
|
|
expect(result.matchedRule).toBe("Bash(rm -rf:*)");
|
|
});
|
|
|
|
// ============================================================================
|
|
// Combined allowedTools and disallowedTools Tests
|
|
// ============================================================================
|
|
|
|
test("disallowedTools takes precedence over allowedTools", () => {
|
|
cliPermissions.setAllowedTools("Bash");
|
|
cliPermissions.setDisallowedTools("Bash(curl:*)");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
// curl should be denied
|
|
const curlResult = checkPermission(
|
|
"Bash",
|
|
{ command: "curl https://example.com" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(curlResult.decision).toBe("deny");
|
|
|
|
// other commands should be allowed
|
|
const lsResult = checkPermission(
|
|
"Bash",
|
|
{ command: "ls" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(lsResult.decision).toBe("allow");
|
|
});
|
|
|
|
test("allowedTools and disallowedTools with multiple tools", () => {
|
|
cliPermissions.setAllowedTools("Bash,Read");
|
|
cliPermissions.setDisallowedTools("Write");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
const bashResult = checkPermission(
|
|
"Bash",
|
|
{ command: "ls" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(bashResult.decision).toBe("allow");
|
|
|
|
const readResult = checkPermission(
|
|
"Read",
|
|
{ file_path: "/tmp/file.txt" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(readResult.decision).toBe("allow");
|
|
|
|
const writeResult = checkPermission(
|
|
"Write",
|
|
{ file_path: "/tmp/file.txt" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(writeResult.decision).toBe("deny");
|
|
});
|
|
|
|
// ============================================================================
|
|
// Precedence Tests
|
|
// ============================================================================
|
|
|
|
test("Precedence: settings deny > CLI disallowedTools", () => {
|
|
cliPermissions.setDisallowedTools("Bash(npm:*)");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: [],
|
|
deny: ["Bash(curl:*)"],
|
|
ask: [],
|
|
};
|
|
|
|
// Settings deny should match first
|
|
const result = checkPermission(
|
|
"Bash",
|
|
{ command: "curl https://example.com" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
|
|
expect(result.decision).toBe("deny");
|
|
expect(result.matchedRule).toBe("Bash(curl:*)");
|
|
expect(result.reason).toBe("Matched deny rule");
|
|
});
|
|
|
|
test("Precedence: CLI allowedTools > settings allow", () => {
|
|
cliPermissions.setAllowedTools("Bash(npm install)");
|
|
|
|
const permissions: PermissionRules = {
|
|
allow: ["Bash(docker:*)"],
|
|
deny: [],
|
|
ask: [],
|
|
};
|
|
|
|
// CLI should match for npm install
|
|
const npmResult = checkPermission(
|
|
"Bash",
|
|
{ command: "npm install" },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(npmResult.decision).toBe("allow");
|
|
expect(npmResult.matchedRule).toBe("Bash(npm install) (CLI)");
|
|
|
|
// Settings should match for docker (non-read-only command)
|
|
const dockerResult = checkPermission(
|
|
"Bash",
|
|
{ command: "docker build ." },
|
|
permissions,
|
|
"/Users/test/project",
|
|
);
|
|
expect(dockerResult.decision).toBe("allow");
|
|
expect(dockerResult.matchedRule).toBe("Bash(docker:*)");
|
|
});
|
|
|
|
test("CLI allowedTools normalizes shell aliases to Bash wildcard", () => {
|
|
cliPermissions.clear();
|
|
cliPermissions.setAllowedTools("run_shell_command");
|
|
|
|
const tools = cliPermissions.getAllowedTools();
|
|
expect(tools).toEqual(["Bash(:*)"]);
|
|
});
|
|
|
|
test("CLI allowedTools normalizes file alias family", () => {
|
|
cliPermissions.clear();
|
|
cliPermissions.setAllowedTools("WriteFileGemini");
|
|
|
|
const tools = cliPermissions.getAllowedTools();
|
|
expect(tools).toEqual(["Write(**)"]);
|
|
});
|