fix: confine Agent and MCP data access

This commit is contained in:
2026-07-10 16:38:08 +08:00
parent 73fa28a178
commit f4968d6657
25 changed files with 1521 additions and 221 deletions
+17 -11
View File
@@ -1,11 +1,14 @@
import { mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
import { mkdir, mkdtemp, readFile, realpath, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { Readable } from "node:stream";
import { describe, expect, it, vi } from "vitest";
import { claudeSdkToolConfigForRole, cphHubMcpToolsForRole } from "../../src/agent/roleTools.js";
import type { FeishuRuntime } from "../../src/feishu/client.js";
import { downloadFeishuMessageResource } from "../../src/feishu/download.js";
const itOnLinux = process.platform === "linux" ? it : it.skip;
describe("Feishu message resource download", () => {
it("exposes the download tool to default and explicitly configured roles", () => {
expect(cphHubMcpToolsForRole(undefined)).toContain("feishu_download_resource");
@@ -15,24 +18,27 @@ describe("Feishu message resource download", () => {
]);
});
it("downloads a bound-chat image into the project inbox", async () => {
const workspaceDir = await mkdtemp(join(tmpdir(), "hub-feishu-download-"));
itOnLinux("downloads a bound-chat image into the project inbox", async () => {
const workspaceRoot = await mkdtemp(join(tmpdir(), "hub-feishu-download-"));
const workspaceDir = join(workspaceRoot, "project");
await mkdir(workspaceDir);
try {
const messageGet = vi.fn(async () => ({ data: { items: [{ chat_id: "chat-1" }] } }));
const resourceWriteFile = vi.fn(async (filePath: string) => {
await writeFile(filePath, "image bytes");
});
const messageResourceGet = vi.fn(async () => ({ writeFile: resourceWriteFile }));
const messageResourceGet = vi.fn(async () => ({
getReadableStream: () => Readable.from([Buffer.from("image bytes")]),
}));
const rt = mockRuntime(messageGet, messageResourceGet);
const result = await downloadFeishuMessageResource(
{ messageId: "message-1", fileKey: "img-key-1", resourceType: "image" },
{ boundChatId: "chat-1", workspaceDir },
{ boundChatId: "chat-1", workspaceRoot, workspaceDir },
rt,
);
expect(result).toMatchObject({ resourceType: "image" });
expect(result.path).toMatch(new RegExp(`^${escapeRegExp(join(workspaceDir, ".cph", "inbox"))}`));
expect(result.path).toMatch(
new RegExp(`^${escapeRegExp(join(await realpath(workspaceDir), ".cph", "inbox"))}`),
);
expect(result.path).toMatch(/\.png$/);
await expect(readFile(result.path, "utf8")).resolves.toBe("image bytes");
expect(messageResourceGet).toHaveBeenCalledWith({
@@ -40,7 +46,7 @@ describe("Feishu message resource download", () => {
path: { message_id: "message-1", file_key: "img-key-1" },
});
} finally {
await rm(workspaceDir, { recursive: true, force: true });
await rm(workspaceRoot, { recursive: true, force: true });
}
});
@@ -51,7 +57,7 @@ describe("Feishu message resource download", () => {
await expect(downloadFeishuMessageResource(
{ messageId: "message-other", fileKey: "img-key-other", resourceType: "image" },
{ boundChatId: "chat-1", workspaceDir: "/tmp/project-1" },
{ boundChatId: "chat-1", workspaceRoot: "/tmp", workspaceDir: "/tmp/project-1" },
rt,
)).rejects.toThrow("current project's bound chat");
expect(messageResourceGet).not.toHaveBeenCalled();