summaryrefslogtreecommitdiffhomepage
path: root/packages/core/src/tools/write-file.ts
diff options
context:
space:
mode:
authorAdam Malczewski <[email protected]>2026-05-27 17:22:52 +0900
committerAdam Malczewski <[email protected]>2026-05-27 17:22:52 +0900
commitda57842686ebfd157396551fc76d0c18f7676335 (patch)
tree31caa165c9c6188ee7fa27663ec832a13f6a7ac2 /packages/core/src/tools/write-file.ts
parent399e1509b93b9f3c56142f94b8fb2c30c2dedb2f (diff)
downloaddispatch-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.ts18
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.`;
}