Skip to content

Commit 391362b

Browse files
fix CRITICAL: ReadFileTool ReferenceError + WriteFile/EditFile path traversal
ReadFileTool.ts line 147: parameter named _ctx but body referenced ctx (undefined) — causes ReferenceError on every read_file call when workspaceStore is missing. WriteFileTool.ts + EditFileTool.ts: resolvePath() had no path traversal check (no '..' sanitization), allowing writes/edits outside workspace root. Added validatePath() with '..' detection + workspace root boundary check, matching the pattern already used in ReadFileTool.ts.
1 parent acf6a1f commit 391362b

3 files changed

Lines changed: 19 additions & 1 deletion

File tree

apps/desktop/src/renderer/runtime/tools/implementations/EditFileTool.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,12 @@ function resolvePath(rootPath: string | null, inputPath: string): string {
103103
return `${rootPath}\\${inputPath.replace(/\//g, '\\')}`
104104
}
105105

106+
function validatePath(normalized: string, rootPath: string | null): string | null {
107+
if (normalized.includes('..')) return 'Path traversal denied'
108+
if (rootPath && !normalized.startsWith(rootPath)) return 'Path escapes workspace root'
109+
return null
110+
}
111+
106112
function toDiffEdits(input: Record<string, unknown>): DiffEdit[] {
107113
const edits = input.edits as Array<Record<string, unknown>> | undefined
108114
if (Array.isArray(edits) && edits.length > 0) {
@@ -282,6 +288,9 @@ export const EditFileTool: AgentTool = buildTool({
282288
const rootPath = ctx.workspaceStore?.rootPath ?? null
283289
const fullPath = resolvePath(rootPath, filePath)
284290

291+
const validationError = validatePath(fullPath, rootPath)
292+
if (validationError) return { data: null, error: validationError, isError: true }
293+
285294
if (isPathDenied(fullPath)) {
286295
return { data: null, error: `File not found: "${filePath}". The file may not exist at this location.`, isError: true }
287296
}

apps/desktop/src/renderer/runtime/tools/implementations/ReadFileTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ export const ReadFileTool: AgentTool = buildTool({
144144
const path = String(input.path ?? '')
145145
if (!path) return { data: null, error: 'path is required', isError: true }
146146

147-
const rootPath = ctx.workspaceStore?.rootPath ?? null
147+
const rootPath = _ctx.workspaceStore?.rootPath ?? null
148148
const fullPath = resolvePath(rootPath, path)
149149

150150
const validationError = validatePath(fullPath, rootPath)

apps/desktop/src/renderer/runtime/tools/implementations/WriteFileTool.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,12 @@ function resolvePath(rootPath: string | null, inputPath: string): string {
8282
return `${rootPath}\\${inputPath.replace(/\//g, '\\')}`
8383
}
8484

85+
function validatePath(normalized: string, rootPath: string | null): string | null {
86+
if (normalized.includes('..')) return 'Path traversal denied'
87+
if (rootPath && !normalized.startsWith(rootPath)) return 'Path escapes workspace root'
88+
return null
89+
}
90+
8591
export const WriteFileTool: AgentTool = buildTool({
8692
name: 'write_file',
8793
description: 'Write content to a file (creates directories if needed), updating the file on disk immediately',
@@ -109,6 +115,9 @@ export const WriteFileTool: AgentTool = buildTool({
109115
const rootPath = ctx.workspaceStore?.rootPath ?? null
110116
const fullPath = resolvePath(rootPath, path)
111117

118+
const validationError = validatePath(fullPath, rootPath)
119+
if (validationError) return { data: null, error: validationError, isError: true }
120+
112121
if (isPathDenied(fullPath)) {
113122
return { data: null, error: `Cannot write to "${path}". The path is not accessible.`, isError: true }
114123
}

0 commit comments

Comments
 (0)