rohitg00/ai-engineering-from-scratch · error · Error
path escapes sandbox
Error message
path escapes sandbox
What it means
Sandbox-containment guard in toolReadFile. It fires from the catch block around realpathSync: the requested path (or the sandbox root itself) could not be resolved to a real filesystem path — typically a missing file, a broken/looping symlink, or a permissions problem — so containment cannot be verified and the read is refused. Despite the wording, it usually means 'unresolvable path', not a proven escape.
Solutions
- Check the candidate exists with existsSync/statSync before resolving and return a clean 'file not found' tool error
- Verify the resolved real path stays under the sandbox root before opening it
- Surface the underlying errno so missing files, symlink loops, and permission errors are distinguishable
- Create/verify the sandbox directory once at startup so the root always resolves
Defensive patterns
Strategy: try-catch
When it happens
Trigger: Thrown at phases/19-capstone-projects/01-terminal-native-coding-agent/code/ts/src/tools.ts:24 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of rohitg00/ai-engineering-from-scratch@39ea8a1c6d (2026-08-26).
Data as JSON: /api/errors/95d966713fe03dd0.
Report an issue: GitHub.
Appendix: source
Thrown at phases/19-capstone-projects/01-terminal-native-coding-agent/code/ts/src/tools.ts:24
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)}`;
}
export const TOOLS: Record<string, ToolFn> = {
read_file: toolReadFile,
run_shell: toolRunShell,View on GitHub (pinned to 39ea8a1c6d)