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

  1. Use only relative paths without '..' segments that stay within the base directory
  2. Remove or fix symlinks that point outside the base directory
  3. 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

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


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/32b3ce35985cb4b5. Report an issue: GitHub.