diff options
| author | Adam Malczewski <[email protected]> | 2026-06-05 21:20:12 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-06-05 21:20:12 +0900 |
| commit | 7fb3269c698ae583ea7997ce206c4ae252fd3218 (patch) | |
| tree | 247d03408ecccd633290ea56b1b08811ebe460ec /packages/tool-read-file/src/read-file.test.ts | |
| parent | 4283d1f8a0bc3953e65962a2364c903d0015f047 (diff) | |
| download | dispatch-7fb3269c698ae583ea7997ce206c4ae252fd3218.tar.gz dispatch-7fb3269c698ae583ea7997ce206c4ae252fd3218.zip | |
feat(backend): credential-store + model selection/catalog (GET /models) + per-turn cwd through orchestrator/transport/host-bin
Diffstat (limited to 'packages/tool-read-file/src/read-file.test.ts')
| -rw-r--r-- | packages/tool-read-file/src/read-file.test.ts | 68 |
1 files changed, 67 insertions, 1 deletions
diff --git a/packages/tool-read-file/src/read-file.test.ts b/packages/tool-read-file/src/read-file.test.ts index f995b09..2725a05 100644 --- a/packages/tool-read-file/src/read-file.test.ts +++ b/packages/tool-read-file/src/read-file.test.ts @@ -11,7 +11,7 @@ import { validateArgs, } from "./read-file.js"; -function stubCtx(): ToolExecuteContext { +function stubCtx(overrides?: Partial<ToolExecuteContext>): ToolExecuteContext { return { toolCallId: "test-call-1", onOutput: () => {}, @@ -21,6 +21,7 @@ function stubCtx(): ToolExecuteContext { { emit: () => {} }, { now: () => 0, newId: () => "id" }, ), + ...overrides, }; } @@ -250,4 +251,69 @@ describe("createReadFileTool", () => { expect(tool.parameters.required).toEqual(["path"]); expect(tool.parameters.properties?.path?.type).toBe("string"); }); + + it("reads file under ctx.cwd when set (not baked workdir)", async () => { + const ctxDir = await mkdtemp(join(tmpdir(), "ctx-cwd-test-")); + try { + const filePath = join(ctxDir, "ctx-file.txt"); + await writeFile(filePath, "from ctx cwd", "utf8"); + + const tool = createReadFileTool(workdir); // baked workdir is different + const result = await tool.execute({ path: "ctx-file.txt" }, stubCtx({ cwd: ctxDir })); + + expect(result.isError).toBeUndefined(); + expect(result.content).toContain("1: from ctx cwd"); + } finally { + await rm(ctxDir, { recursive: true, force: true }); + } + }); + + it("rejects path escaping ctx.cwd via ..", async () => { + const ctxDir = await mkdtemp(join(tmpdir(), "ctx-escape-test-")); + try { + const tool = createReadFileTool(workdir); + const result = await tool.execute({ path: "../escape.txt" }, stubCtx({ cwd: ctxDir })); + + expect(result.isError).toBe(true); + expect(result.content).toContain("outside the working directory"); + } finally { + await rm(ctxDir, { recursive: true, force: true }); + } + }); + + it("rejects symlink escaping ctx.cwd", async () => { + const ctxDir = await mkdtemp(join(tmpdir(), "ctx-symlink-test-")); + const outsideDir = await mkdtemp(join(tmpdir(), "ctx-outside-")); + try { + const outsideFile = join(outsideDir, "secret.txt"); + await writeFile(outsideFile, "secret data", "utf8"); + + const symlinkPath = join(ctxDir, "link.txt"); + const { symlink } = await import("node:fs/promises"); + await symlink(outsideFile, symlinkPath); + + const tool = createReadFileTool(workdir); + const result = await tool.execute({ path: "link.txt" }, stubCtx({ cwd: ctxDir })); + + expect(result.isError).toBe(true); + expect(result.content).toContain("outside the working directory"); + } finally { + await rm(ctxDir, { recursive: true, force: true }); + await rm(outsideDir, { recursive: true, force: true }); + } + }); + + it("falls back to baked workdir when ctx.cwd is omitted", async () => { + const filePath = join(workdir, "baked-file.txt"); + await writeFile(filePath, "from baked workdir", "utf8"); + + const tool = createReadFileTool(workdir); + const ctx = stubCtx(); + // Ensure cwd is undefined + expect(ctx.cwd).toBeUndefined(); + const result = await tool.execute({ path: "baked-file.txt" }, ctx); + + expect(result.isError).toBeUndefined(); + expect(result.content).toContain("1: from baked workdir"); + }); }); |
