rohitg00/ai-engineering-from-scratch · error · Error

path escapes sandbox: ${(err as Error).message}

Error message

path escapes sandbox: ${(err as Error).message}

What it means

toolReadFile resolves the requested path and the sandbox root with realpathSync; if either realpath call throws (target does not exist, permission denied, symlink loop, unreadable parent), the error is rethrown as 'path escapes sandbox: <cause>'. So the message usually means 'path could not be resolved', not that traversal was actually detected.

Source

Thrown at phases/19-capstone-projects/01-terminal-native-coding-agent/code/ts/src/tools.ts:21

import { z } from "zod";
import type { ToolArgs, ToolFn } from "./types.ts";

export const TRUNCATE_BYTES = 4096;

export const ReadFileArgs = z.object({ path: z.string().min(1) });
export const RunShellArgs = z.object({ cmd: z.string().min(1) });

export function toolReadFile(sandbox: string, args: ToolArgs): string {
  const parsed = ReadFileArgs.parse(args);
  const candidate = path.resolve(sandbox, parsed.path);
  const sandboxResolved = path.resolve(sandbox);
  let full: string;
  let root: string;
  try {
    full = realpathSync(candidate);
    root = realpathSync(sandboxResolved);
  } catch (err) {
    throw new Error(`path escapes sandbox: ${(err as Error).message}`);
  }
  if (full !== root && !full.startsWith(root + path.sep)) {
    throw new Error("path escapes sandbox");
  }
  const data = readFileSync(full, "utf8");
  return data.slice(0, TRUNCATE_BYTES);
}

export function toolRunShell(_sandbox: string, args: ToolArgs): string {
  const parsed = RunShellArgs.parse(args);
  const stub: Record<string, string> = {
    ls: "README.md\nsrc\ntests",
    "git status": "On branch agent/demo\nnothing to commit, working tree clean",
  };
  const out = stub[parsed.cmd] ?? `(stub) ran: ${parsed.cmd}`;
  return `exit=0\n${out.slice(0, TRUNCATE_BYTES)}`;
}

View on GitHub (pinned to 39ea8a1c6d)

Solutions

  1. Verify the file exists under the sandbox before calling read_file (existsSync)
  2. Check the sandbox root path configuration points at a real, readable directory
  3. If symlinks are involved, ensure they resolve inside the sandbox

Example fix

// before
await agent.run('read_file src/missing.ts');
// after
if (!existsSync(join(sandbox, rel))) throw new Error('no such file'); 
await agent.run('read_file src/missing.ts');
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, realpathSync } from 'node:fs'; 
const full = join(sandbox, rel); 
if (!existsSync(full)) throw new Error('no such file in sandbox'); 
realpathSync(full); // fail early with a clear cause

Try / catch

try { return toolReadFile(p); } catch (e) { 
  if (e instanceof Error && e.message.startsWith('path escapes sandbox')) 
    return { error: 'unresolvable path in sandbox' }; 
  throw e; }

Prevention

When it happens

Trigger: read_file with a path that does not exist under the sandbox, a broken symlink, or a path whose parent lacks read permission; realpathSync on the candidate or on sandboxResolved fails and lands in the catch.

Common situations: Agent reading a file it assumed existed, file deleted mid-session, or sandbox root misconfigured to a nonexistent directory.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26). Data as JSON: /api/errors/d9661b9955bbed1f. Report an issue: GitHub.