133 lines
4.4 KiB
TypeScript
133 lines
4.4 KiB
TypeScript
import { describe, expect, it } from "bun:test";
|
|
import { mkdtemp, mkdir, rm, symlink } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { dirname, join } from "node:path";
|
|
|
|
import {
|
|
canAutoApprovePermission,
|
|
resolvePermissionApproval,
|
|
} from "../../src/routes/chatPermissionPolicy.js";
|
|
|
|
describe("permission approval policy", () => {
|
|
it.each([
|
|
"show_chart",
|
|
"web_search",
|
|
"skill",
|
|
])("allows low-risk permission %s", (permission) => {
|
|
expect(canAutoApprovePermission(permission)).toBe(true);
|
|
});
|
|
|
|
it("allows structured searches within the workspace", () => {
|
|
const workspaceRoot = process.cwd();
|
|
const context = {
|
|
workspaceRoot,
|
|
metadata: { path: join(workspaceRoot, "src"), include: "*.ts" },
|
|
patterns: ["*.ts"],
|
|
};
|
|
expect(canAutoApprovePermission("glob", context)).toBe(true);
|
|
expect(canAutoApprovePermission("grep", context)).toBe(true);
|
|
});
|
|
|
|
it("allows a glob with an explicit safe prefix from the workspace root", () => {
|
|
expect(
|
|
canAutoApprovePermission("glob", {
|
|
workspaceRoot: process.cwd(),
|
|
metadata: { path: process.cwd(), pattern: "src/**/*.ts" },
|
|
patterns: ["src/**/*.ts"],
|
|
}),
|
|
).toBe(true);
|
|
});
|
|
|
|
it.each([
|
|
{ metadata: { path: dirname(process.cwd()) }, patterns: ["*"] },
|
|
{ metadata: { path: join(process.cwd(), ".local.env") }, patterns: ["*"] },
|
|
{ metadata: { path: join(process.cwd(), "data") }, patterns: ["*"] },
|
|
{ metadata: { path: join(process.cwd(), "src") }, patterns: ["../logs/**"] },
|
|
{ metadata: { path: process.cwd() }, patterns: ["**/*.env"] },
|
|
{ metadata: { path: process.cwd() }, patterns: ["**/*"] },
|
|
{ metadata: { path: join(process.cwd(), "src") }, patterns: [".[e]nv"] },
|
|
])("keeps protected or external searches interactive", (request) => {
|
|
expect(
|
|
canAutoApprovePermission("glob", {
|
|
workspaceRoot: process.cwd(),
|
|
...request,
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("keeps grep from the workspace root interactive", () => {
|
|
expect(
|
|
canAutoApprovePermission("grep", {
|
|
workspaceRoot: process.cwd(),
|
|
metadata: { path: process.cwd(), include: "*.ts" },
|
|
patterns: ["secret"],
|
|
}),
|
|
).toBe(false);
|
|
});
|
|
|
|
it("rejects a workspace symlink that resolves outside the workspace", async () => {
|
|
const workspaceRoot = await mkdtemp(join(tmpdir(), "permission-workspace-"));
|
|
const externalRoot = await mkdtemp(join(tmpdir(), "permission-external-"));
|
|
try {
|
|
await mkdir(join(externalRoot, "src"));
|
|
const linkedPath = join(workspaceRoot, "linked");
|
|
await symlink(join(externalRoot, "src"), linkedPath, "dir");
|
|
|
|
expect(
|
|
canAutoApprovePermission("grep", {
|
|
workspaceRoot,
|
|
metadata: { path: linkedPath, include: "*.ts" },
|
|
patterns: ["secret"],
|
|
}),
|
|
).toBe(false);
|
|
} finally {
|
|
await Promise.all([
|
|
rm(workspaceRoot, { force: true, recursive: true }),
|
|
rm(externalRoot, { force: true, recursive: true }),
|
|
]);
|
|
}
|
|
});
|
|
|
|
it("rejects protected descendants below an otherwise safe search root", async () => {
|
|
const workspaceRoot = await mkdtemp(join(tmpdir(), "permission-descendant-"));
|
|
try {
|
|
const sourceRoot = join(workspaceRoot, "src");
|
|
await mkdir(join(sourceRoot, "data"), { recursive: true });
|
|
|
|
expect(
|
|
canAutoApprovePermission("grep", {
|
|
workspaceRoot,
|
|
metadata: { path: sourceRoot, include: "*.ts" },
|
|
patterns: ["secret"],
|
|
}),
|
|
).toBe(false);
|
|
} finally {
|
|
await rm(workspaceRoot, { force: true, recursive: true });
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
"bash",
|
|
"edit",
|
|
"external_directory",
|
|
"store_render_ref",
|
|
"tjwater_server_query",
|
|
"tjwater_tjwater_server_query",
|
|
])(
|
|
"requires confirmation for permission %s",
|
|
(permission) => {
|
|
expect(canAutoApprovePermission(permission)).toBe(false);
|
|
},
|
|
);
|
|
|
|
it("resolves request, auto, and always modes", () => {
|
|
expect(resolvePermissionApproval("request", "show_chart").autoApprove).toBe(false);
|
|
expect(resolvePermissionApproval("auto", "show_chart").autoApprove).toBe(true);
|
|
expect(resolvePermissionApproval("auto", "bash").autoApprove).toBe(false);
|
|
expect(resolvePermissionApproval("always", "bash")).toMatchObject({
|
|
autoApprove: true,
|
|
title: "已按始终允许模式放行",
|
|
});
|
|
});
|
|
});
|