diff --git a/packages/server/src/tools.ts b/packages/server/src/tools.ts index ee7dbdd..d94eb3c 100644 --- a/packages/server/src/tools.ts +++ b/packages/server/src/tools.ts @@ -7,15 +7,24 @@ import * as fs from "node:fs"; import * as path from "node:path"; -import { execSync, spawn } from "node:child_process"; +import { execSync } from "node:child_process"; import * as os from "node:os"; +/** Sandbox: restrict file ops to this base directory or env GCA_WORKSPACE */ +const BASE_DIR = path.resolve(process.env.GCA_WORKSPACE ?? process.cwd()); + +function safePath(p: string): string { + const resolved = path.resolve(BASE_DIR, p); + if (!resolved.startsWith(BASE_DIR)) throw new Error(`Path traversal blocked: ${p}`); + return resolved; +} + // ── C-002: file_list ──────────────────────────────────────── export async function file_list(args: { path: string; recursive?: boolean; }): Promise { - const dir = args.path || "."; + const dir = safePath(args.path || "."); const entries = fs.readdirSync(dir, { withFileTypes: true }); const result = entries.map(e => ({ name: e.name, @@ -66,11 +75,12 @@ export async function file_move(args: { } // ── C-008: exec ───────────────────────────────────────────── -export async function exec(args: { - command: string; - cwd?: string; - timeout?: number; -}): Promise { +function sanitizeCommand(cmd: string): string { + if (/[;&|`$(){}[\]<>!\\\n\r]/.test(cmd)) throw new Error(`Dangerous chars in: ${cmd.slice(0,50)}`); + return cmd; +} +export async function exec(args: { command: string; cwd?: string; timeout?: number }): Promise { + const command = sanitizeCommand(args.command); const timeout = args.timeout ?? 30000; try { const stdout = execSync(args.command, {