mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 17:04:58 +00:00
fix(acp): harden permission tool-name validation
This commit is contained in:
@@ -87,6 +87,75 @@ describe("resolvePermissionRequest", () => {
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject" } });
|
||||
});
|
||||
|
||||
it("auto-approves read when rawInput path resolves inside cwd", async () => {
|
||||
const prompt = vi.fn(async () => true);
|
||||
const res = await resolvePermissionRequest(
|
||||
makePermissionRequest({
|
||||
toolCall: {
|
||||
toolCallId: "tool-read-inside-cwd",
|
||||
title: "read: ignored-by-raw-input",
|
||||
status: "pending",
|
||||
rawInput: { path: "docs/security.md" },
|
||||
},
|
||||
}),
|
||||
{ prompt, log: () => {}, cwd: "/tmp/openclaw-acp-cwd" },
|
||||
);
|
||||
expect(prompt).not.toHaveBeenCalled();
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "allow" } });
|
||||
});
|
||||
|
||||
it("auto-approves read when rawInput file URL resolves inside cwd", async () => {
|
||||
const prompt = vi.fn(async () => true);
|
||||
const res = await resolvePermissionRequest(
|
||||
makePermissionRequest({
|
||||
toolCall: {
|
||||
toolCallId: "tool-read-inside-cwd-file-url",
|
||||
title: "read: ignored-by-raw-input",
|
||||
status: "pending",
|
||||
rawInput: { path: "file:///tmp/openclaw-acp-cwd/docs/security.md" },
|
||||
},
|
||||
}),
|
||||
{ prompt, log: () => {}, cwd: "/tmp/openclaw-acp-cwd" },
|
||||
);
|
||||
expect(prompt).not.toHaveBeenCalled();
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "allow" } });
|
||||
});
|
||||
|
||||
it("prompts for read when rawInput path escapes cwd via traversal", async () => {
|
||||
const prompt = vi.fn(async () => false);
|
||||
const res = await resolvePermissionRequest(
|
||||
makePermissionRequest({
|
||||
toolCall: {
|
||||
toolCallId: "tool-read-escape-cwd",
|
||||
title: "read: ignored-by-raw-input",
|
||||
status: "pending",
|
||||
rawInput: { path: "../.ssh/id_rsa" },
|
||||
},
|
||||
}),
|
||||
{ prompt, log: () => {}, cwd: "/tmp/openclaw-acp-cwd/workspace" },
|
||||
);
|
||||
expect(prompt).toHaveBeenCalledTimes(1);
|
||||
expect(prompt).toHaveBeenCalledWith("read", "read: ignored-by-raw-input");
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject" } });
|
||||
});
|
||||
|
||||
it("prompts for read when scoped path is missing", async () => {
|
||||
const prompt = vi.fn(async () => false);
|
||||
const res = await resolvePermissionRequest(
|
||||
makePermissionRequest({
|
||||
toolCall: {
|
||||
toolCallId: "tool-read-no-path",
|
||||
title: "read",
|
||||
status: "pending",
|
||||
},
|
||||
}),
|
||||
{ prompt, log: () => {} },
|
||||
);
|
||||
expect(prompt).toHaveBeenCalledTimes(1);
|
||||
expect(prompt).toHaveBeenCalledWith("read", "read");
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject" } });
|
||||
});
|
||||
|
||||
it("prompts for non-core read-like tool names", async () => {
|
||||
const prompt = vi.fn(async () => false);
|
||||
const res = await resolvePermissionRequest(
|
||||
@@ -176,6 +245,59 @@ describe("resolvePermissionRequest", () => {
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "allow" } });
|
||||
});
|
||||
|
||||
it("prompts when metadata tool name contains invalid characters", async () => {
|
||||
const prompt = vi.fn(async () => false);
|
||||
const res = await resolvePermissionRequest(
|
||||
makePermissionRequest({
|
||||
toolCall: {
|
||||
toolCallId: "tool-invalid-meta",
|
||||
title: "read: src/index.ts",
|
||||
status: "pending",
|
||||
_meta: { toolName: "read.*" },
|
||||
},
|
||||
}),
|
||||
{ prompt, log: () => {} },
|
||||
);
|
||||
expect(prompt).toHaveBeenCalledTimes(1);
|
||||
expect(prompt).toHaveBeenCalledWith(undefined, "read: src/index.ts");
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject" } });
|
||||
});
|
||||
|
||||
it("prompts when raw input tool name exceeds max length", async () => {
|
||||
const prompt = vi.fn(async () => false);
|
||||
const res = await resolvePermissionRequest(
|
||||
makePermissionRequest({
|
||||
toolCall: {
|
||||
toolCallId: "tool-long-raw",
|
||||
title: "read: src/index.ts",
|
||||
status: "pending",
|
||||
rawInput: { toolName: "r".repeat(129) },
|
||||
},
|
||||
}),
|
||||
{ prompt, log: () => {} },
|
||||
);
|
||||
expect(prompt).toHaveBeenCalledTimes(1);
|
||||
expect(prompt).toHaveBeenCalledWith(undefined, "read: src/index.ts");
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject" } });
|
||||
});
|
||||
|
||||
it("prompts when title tool name contains non-allowed characters", async () => {
|
||||
const prompt = vi.fn(async () => false);
|
||||
const res = await resolvePermissionRequest(
|
||||
makePermissionRequest({
|
||||
toolCall: {
|
||||
toolCallId: "tool-bad-title-name",
|
||||
title: "read🚀: src/index.ts",
|
||||
status: "pending",
|
||||
},
|
||||
}),
|
||||
{ prompt, log: () => {} },
|
||||
);
|
||||
expect(prompt).toHaveBeenCalledTimes(1);
|
||||
expect(prompt).toHaveBeenCalledWith(undefined, "read🚀: src/index.ts");
|
||||
expect(res).toEqual({ outcome: { outcome: "selected", optionId: "reject" } });
|
||||
});
|
||||
|
||||
it("returns cancelled when no permission options are present", async () => {
|
||||
const prompt = vi.fn(async () => true);
|
||||
const res = await resolvePermissionRequest(makePermissionRequest({ options: [] }), {
|
||||
|
||||
Reference in New Issue
Block a user