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
- Verify the file exists under the sandbox before calling read_file (existsSync)
- Check the sandbox root path configuration points at a real, readable directory
- 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
- ExistsCheck and resolve paths before invoking the tool
- Keep the sandbox root a real, readable directory; validate it at startup
- Distinguish 'missing file' from actual traversal attempts in error handling
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.