{"record":{"id":"95d966713fe03dd0","repo":"rohitg00/ai-engineering-from-scratch","slug":"path-escapes-sandbox","errorCode":null,"errorMessage":"path escapes sandbox","messagePattern":"path escapes sandbox","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"phases/19-capstone-projects/01-terminal-native-coding-agent/code/ts/src/tools.ts","lineNumber":24,"sourceCode":"export const TRUNCATE_BYTES = 4096;\n\nexport const ReadFileArgs = z.object({ path: z.string().min(1) });\nexport const RunShellArgs = z.object({ cmd: z.string().min(1) });\n\nexport function toolReadFile(sandbox: string, args: ToolArgs): string {\n  const parsed = ReadFileArgs.parse(args);\n  const candidate = path.resolve(sandbox, parsed.path);\n  const sandboxResolved = path.resolve(sandbox);\n  let full: string;\n  let root: string;\n  try {\n    full = realpathSync(candidate);\n    root = realpathSync(sandboxResolved);\n  } catch (err) {\n    throw new Error(`path escapes sandbox: ${(err as Error).message}`);\n  }\n  if (full !== root && !full.startsWith(root + path.sep)) {\n    throw new Error(\"path escapes sandbox\");\n  }\n  const data = readFileSync(full, \"utf8\");\n  return data.slice(0, TRUNCATE_BYTES);\n}\n\nexport function toolRunShell(_sandbox: string, args: ToolArgs): string {\n  const parsed = RunShellArgs.parse(args);\n  const stub: Record<string, string> = {\n    ls: \"README.md\\nsrc\\ntests\",\n    \"git status\": \"On branch agent/demo\\nnothing to commit, working tree clean\",\n  };\n  const out = stub[parsed.cmd] ?? `(stub) ran: ${parsed.cmd}`;\n  return `exit=0\\n${out.slice(0, TRUNCATE_BYTES)}`;\n}\n\nexport const TOOLS: Record<string, ToolFn> = {\n  read_file: toolReadFile,\n  run_shell: toolRunShell,","sourceCodeStart":6,"sourceCodeEnd":42,"githubUrl":"https://github.com/rohitg00/ai-engineering-from-scratch/blob/39ea8a1c6d0b61f071226eff7ede4d4105fed820/phases/19-capstone-projects/01-terminal-native-coding-agent/code/ts/src/tools.ts#L6-L42","documentation":"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.","triggerScenarios":"Thrown at phases/19-capstone-projects/01-terminal-native-coding-agent/code/ts/src/tools.ts:24 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","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"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"39ea8a1c6d0b61f071226eff7ede4d4105fed820","analyzedAt":"2026-08-26T03:13:46.626Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}