diff options
| author | Adam Malczewski <[email protected]> | 2026-05-27 17:22:52 +0900 |
|---|---|---|
| committer | Adam Malczewski <[email protected]> | 2026-05-27 17:22:52 +0900 |
| commit | da57842686ebfd157396551fc76d0c18f7676335 (patch) | |
| tree | 31caa165c9c6188ee7fa27663ec832a13f6a7ac2 /packages/core/src/tools/write-file.ts | |
| parent | 399e1509b93b9f3c56142f94b8fb2c30c2dedb2f (diff) | |
| download | dispatch-da57842686ebfd157396551fc76d0c18f7676335.tar.gz dispatch-da57842686ebfd157396551fc76d0c18f7676335.zip | |
feat: tool-output truncation+spill, read_file pagination, read_file_slice, symlink-safe path resolution
Diffstat (limited to 'packages/core/src/tools/write-file.ts')
| -rw-r--r-- | packages/core/src/tools/write-file.ts | 18 |
1 files changed, 14 insertions, 4 deletions
diff --git a/packages/core/src/tools/write-file.ts b/packages/core/src/tools/write-file.ts index 23bc72a..763b083 100644 --- a/packages/core/src/tools/write-file.ts +++ b/packages/core/src/tools/write-file.ts @@ -1,7 +1,8 @@ import { mkdir, writeFile } from "node:fs/promises"; -import { dirname, join, resolve } from "node:path"; +import { dirname } from "node:path"; import { z } from "zod"; import type { ToolDefinition } from "../types/index.js"; +import { canonicalize } from "./path-utils.js"; export function createWriteFileTool(workingDirectory: string): ToolDefinition { return { @@ -14,10 +15,19 @@ export function createWriteFileTool(workingDirectory: string): ToolDefinition { execute: async (args: Record<string, unknown>): Promise<string> => { const filePath = args.path as string; const content = args.content as string; - const absolutePath = resolve(join(workingDirectory, filePath)); - const absoluteWorkDir = resolve(workingDirectory); + // Canonicalize so a workdir-relative path that resolves through + // symlinks to outside the workdir is detected and blocked. The + // canonicalize walks up to the nearest existing ancestor when the + // leaf doesn't exist (typical for write_file), so a path like + // `workdir/escape-link/new-file.txt` where `escape-link` symlinks + // to /etc still resolves through the symlink and is caught here. + const absolutePath = await canonicalize(workingDirectory, filePath); + const absoluteWorkDir = await canonicalize(workingDirectory); - if (!absolutePath.startsWith(`${absoluteWorkDir}/`) && absolutePath !== absoluteWorkDir) { + if ( + absolutePath !== absoluteWorkDir && + !absolutePath.startsWith(`${absoluteWorkDir}/`) + ) { return `Error: Path "${filePath}" is outside the working directory.`; } |
