{"record":{"id":"32b3ce35985cb4b5","repo":"n8n-io/n8n","slug":"path-relativepath-escapes-the-base-directory","errorCode":null,"errorMessage":"Path \"${relativePath}\" escapes the base directory","messagePattern":"Path \"(.+?)\" escapes the base directory","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/@n8n/computer-use/src/tools/filesystem/fs-utils.ts","lineNumber":244,"sourceCode":"\t\t\t\tconst lstat = await fs.lstat(next);\n\t\t\t\tif (lstat.isSymbolicLink()) {\n\t\t\t\t\t// Dangling symlink — follow it manually and continue the walk.\n\t\t\t\t\tconst target = await fs.readlink(next);\n\t\t\t\t\tcurrent = path.resolve(current, target);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// lstat also failed — the path truly does not exist.\n\t\t\t}\n\n\t\t\t// Path does not exist and is not a symlink; append remaining parts as-is.\n\t\t\tcurrent = path.join(current, ...parts.slice(i));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!current.startsWith(realBase + path.sep) && current !== realBase) {\n\t\tthrow new Error(`Path \"${relativePath}\" escapes the base directory`);\n\t}\n\n\t// Check if the resolved real path targets a protected path (e.g. settings directory).\n\t// This catches symlink-based bypasses since `current` has all symlinks resolved.\n\tif (isProtectedSettingsPath(current)) {\n\t\tthrow new Error(`Access denied: cannot access \"${relativePath}\"`);\n\t}\n\n\treturn { absolutePath: absolute, realBasePath: realBase, resolvedPath: current };\n}\n\nexport async function resolveSafePath(basePath: string, relativePath: string): Promise<string> {\n\tconst { absolutePath } = await resolveSafePathDetails(basePath, relativePath);\n\treturn absolutePath;\n}\n\nexport async function resolveReadablePath(basePath: string, relativePath: string): Promise<string> {\n\tconst { absolutePath, realBasePath, resolvedPath } = await resolveSafePathDetails(","sourceCodeStart":226,"sourceCodeEnd":262,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/computer-use/src/tools/filesystem/fs-utils.ts#L226-L262","documentation":"Thrown by resolveSafePathDetails() when the fully resolved path (with all symlinks followed via fs.realpath on each component) does not start with realBase + path.sep. This is the core path-traversal guard — it catches both literal '..' traversal and symlink chains that escape the base directory. The guard walks each path component individually, resolving symlinks incrementally, and checks the final resolved real path against the base.","triggerScenarios":"A tool call with a relative path containing '..' that resolves outside the base directory (e.g. '../../etc/passwd'), or a symlink inside the base directory that points to a location outside it. The incremental realpath walk catches symlink chains at any depth.","commonSituations":"Agent constructs a path with '..' segments, or a symlink in the project root points to a system directory or another project.","solutions":["Use only relative paths without '..' segments that stay within the base directory","Remove or fix symlinks that point outside the base directory","Verify the path resolves inside the base directory before passing it to a tool"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"import * as path from 'node:path';\n\nfunction staysInBase(basePath: string, relativePath: string): boolean {\n  const resolved = path.resolve(basePath, relativePath);\n  return resolved.startsWith(path.resolve(basePath) + path.sep) || resolved === path.resolve(basePath);\n}\n\n// Before calling any filesystem tool:\nif (!staysInBase(dir, filePath)) {\n  throw new Error(`Path \"${filePath}\" would escape the base directory`);\n}","typeGuard":"function isPathEscapeError(e: unknown): boolean {\n  return e instanceof Error && (e.message.includes('escapes the base directory'));\n}","tryCatchPattern":null,"preventionTips":["Use only relative paths without '..' segments","Audit symlinks in the base directory for any that resolve outside it","Resolve paths with path.resolve and verify containment before passing to tools"],"tags":["filesystem","security","computer-use"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}