diff --git a/packages/cli/vitest.config.ts b/packages/cli/vitest.config.ts index 6c9b37434..cf0b05683 100644 --- a/packages/cli/vitest.config.ts +++ b/packages/cli/vitest.config.ts @@ -47,6 +47,10 @@ export default defineConfig({ find: "@aoagents/ao-plugin-agent-opencode", replacement: resolve(__dirname, "../plugins/agent-opencode/src/index.ts"), }, + { + find: "@aoagents/ao-plugin-agent-cursor", + replacement: resolve(__dirname, "../plugins/agent-cursor/src/index.ts"), + }, { find: "@aoagents/ao-plugin-scm-github", replacement: resolve(__dirname, "../plugins/scm-github/src/index.ts"), diff --git a/packages/plugins/agent-cursor/src/index.test.ts b/packages/plugins/agent-cursor/src/index.test.ts index 550da2d87..a962667b3 100644 --- a/packages/plugins/agent-cursor/src/index.test.ts +++ b/packages/plugins/agent-cursor/src/index.test.ts @@ -14,8 +14,9 @@ vi.mock("node:fs/promises", async (importOriginal) => { }); // Mock fs (sync) for getLaunchCommand systemPromptFile tests -const { mockReadFileSync } = vi.hoisted(() => ({ +const { mockReadFileSync, mockLstatSync } = vi.hoisted(() => ({ mockReadFileSync: vi.fn(), + mockLstatSync: vi.fn().mockReturnValue({ isSymbolicLink: () => false }), })); vi.mock("node:fs", async (importOriginal) => { @@ -23,6 +24,7 @@ vi.mock("node:fs", async (importOriginal) => { return { ...actual, readFileSync: mockReadFileSync, + lstatSync: mockLstatSync, }; }); @@ -256,6 +258,17 @@ describe("getLaunchCommand", () => { ); expect(cmd).toBe("agent -- 'Do the task'"); }); + + it("rejects symlinked systemPromptFile for security", () => { + mockLstatSync.mockReturnValueOnce({ isSymbolicLink: () => true }); + mockReadFileSync.mockReturnValueOnce("Should not be read"); + const cmd = agent.getLaunchCommand( + makeLaunchConfig({ systemPromptFile: "/path/to/symlink.txt", prompt: "Do the task" }), + ); + // Should skip the symlinked file and only include the prompt + expect(cmd).toBe("agent -- 'Do the task'"); + expect(mockReadFileSync).not.toHaveBeenCalled(); + }); }); // ========================================================================= diff --git a/packages/plugins/agent-cursor/src/index.ts b/packages/plugins/agent-cursor/src/index.ts index 407276f6b..0af97d495 100644 --- a/packages/plugins/agent-cursor/src/index.ts +++ b/packages/plugins/agent-cursor/src/index.ts @@ -24,7 +24,7 @@ import { import { execFile, execFileSync } from "node:child_process"; import { promisify } from "node:util"; import { stat, access, readFile, lstat } from "node:fs/promises"; -import { readFileSync, constants } from "node:fs"; +import { readFileSync, lstatSync, constants } from "node:fs"; import { join, resolve } from "node:path"; const execFileAsync = promisify(execFile); @@ -101,10 +101,15 @@ async function extractCursorSummary(workspacePath: string): Promise