n8n-io/n8n · error · Error
Path "${relativePath}" escapes the base directory
Error message
Path "${relativePath}" escapes the base directory What it means
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.
Source
Thrown at packages/@n8n/computer-use/src/tools/filesystem/fs-utils.ts:244
const lstat = await fs.lstat(next);
if (lstat.isSymbolicLink()) {
// Dangling symlink — follow it manually and continue the walk.
const target = await fs.readlink(next);
current = path.resolve(current, target);
continue;
}
} catch {
// lstat also failed — the path truly does not exist.
}
// Path does not exist and is not a symlink; append remaining parts as-is.
current = path.join(current, ...parts.slice(i));
break;
}
}
if (!current.startsWith(realBase + path.sep) && current !== realBase) {
throw new Error(`Path "${relativePath}" escapes the base directory`);
}
// Check if the resolved real path targets a protected path (e.g. settings directory).
// This catches symlink-based bypasses since `current` has all symlinks resolved.
if (isProtectedSettingsPath(current)) {
throw new Error(`Access denied: cannot access "${relativePath}"`);
}
return { absolutePath: absolute, realBasePath: realBase, resolvedPath: current };
}
export async function resolveSafePath(basePath: string, relativePath: string): Promise<string> {
const { absolutePath } = await resolveSafePathDetails(basePath, relativePath);
return absolutePath;
}
export async function resolveReadablePath(basePath: string, relativePath: string): Promise<string> {
const { absolutePath, realBasePath, resolvedPath } = await resolveSafePathDetails(View on GitHub (pinned to 5ac6606e81)
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
Defensive patterns
Strategy: validation
Validate before calling
import * as path from 'node:path';
function staysInBase(basePath: string, relativePath: string): boolean {
const resolved = path.resolve(basePath, relativePath);
return resolved.startsWith(path.resolve(basePath) + path.sep) || resolved === path.resolve(basePath);
}
// Before calling any filesystem tool:
if (!staysInBase(dir, filePath)) {
throw new Error(`Path "${filePath}" would escape the base directory`);
} Type guard
function isPathEscapeError(e: unknown): boolean {
return e instanceof Error && (e.message.includes('escapes the base directory'));
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: Agent constructs a path with '..' segments, or a symlink in the project root points to a system directory or another project.
Related errors
- Access denied: "${excludedSegment}" is excluded from filesys
- Access denied: cannot access "${relativePath}"
- Pattern "${pattern}" escapes the base directory
- Invalid skill at ${sourceDirectory}: ${errors.join('; ')}
- User permanently denied access to ${resource.toolGroup}: ${r
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/32b3ce35985cb4b5.
Report an issue: GitHub.