n8n-io/n8n · critical · Error

Path traversal detected, refusing to join paths: ${parentPat

Error message

Path traversal detected, refusing to join paths: ${parentPath} and ${JSON.stringify(paths)}

What it means

Thrown by safeJoinPath in the community-package scanner when path.join(parentPath, ...paths) produces a result that is NOT contained within parentPath. The guard exists to prevent path traversal: an attacker-controlled path component (e.g. '../../etc/passwd' or an absolute path) must not let the scanner read or write outside the sandboxed temp directory during package scanning.

Source

Thrown at packages/@n8n/scan-community-package/scanner/scanner.mjs:47

	if (parentPath === childPath) {
		return true;
	}

	return childPath.startsWith(parentPath + path.sep);
}

/**
 * Joins the given paths to the parentPath, ensuring that the resulting path
 * is still contained within the parentPath. If not, it throws an error to
 * prevent path traversal vulnerabilities.
 *
 * @throws {UnexpectedError} If the resulting path is not contained within the parentPath.
 */
export function safeJoinPath(parentPath, ...paths) {
	const candidate = path.join(parentPath, ...paths);

	if (!isContainedWithin(parentPath, candidate)) {
		throw new Error(
			`Path traversal detected, refusing to join paths: ${parentPath} and ${JSON.stringify(paths)}`,
		);
	}

	return candidate;
}

export const resolvePackage = (packageSpec) => {
	// Validate input to prevent command injection
	if (!/^[a-zA-Z0-9@/_.-]+$/.test(packageSpec)) {
		throw new Error('Invalid package specification');
	}

	let packageName, version;
	if (packageSpec.startsWith('@')) {
		if (packageSpec.includes('@', 1)) {
			// Handle scoped packages with versions
			const lastAtIndex = packageSpec.lastIndexOf('@');

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Sanitize packageName and version upstream in resolvePackage (the regex at scanner.mjs:58 already rejects most traversal chars - extend it if a new vector slips through).
  2. Use realpaths for both parentPath and candidate before the containment check so symlink-resolved paths are compared.
  3. Reject any path segment equal to '..' or starting with '/' before calling safeJoinPath.
  4. Run the scanner in a container with a read-only root and a throwaway TEMP_DIR so even a traversal has nowhere to go.

Example fix

// before
const packageDir = safeJoinPath(TEMP_DIR, `${packageName}-${version}`);

// after - normalize and reject traversal explicitly before joining
const segment = `${packageName}-${version}`;
if (segment.includes('..') || path.isAbsolute(segment)) {
  throw new Error(`Refusing suspicious package dir segment: ${segment}`);
}
const packageDir = safeJoinPath(TEMP_DIR, segment);
Defensive patterns

Strategy: validation

Validate before calling

import path from 'node:path';

function isSafeSegment(segment: string): boolean {
  if (segment.includes('..')) return false;
  if (path.isAbsolute(segment)) return false;
  return true;
}

// run before safeJoinPath
for (const p of paths) {
  if (!isSafeSegment(String(p))) {
    throw new Error(`Refusing unsafe path segment: ${String(p)}`);
  }
}

Type guard

function isContained(parent: string, child: string): boolean {
  const rel = path.relative(parent, child);
  return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
}

Try / catch

try {
  const dir = safeJoinPath(TEMP_DIR, segment);
} catch (e) {
  // Treat as untrusted input - log the segment, reject the package, do NOT retry with normalization.
  logger.warn('Rejecting package with unsafe path segment', { segment, err: (e as Error).message });
  throw new Error('Package rejected: path traversal in name or version');
}

Prevention

When it happens

Trigger: Calling safeJoinPath(TEMP_DIR, packageName, version) where packageName or a derived segment contains '..' segments, an absolute path, or a symlink that resolves outside TEMP_DIR. Also triggered if TEMP_DIR itself is a symlink and isContainedWithin compares lexical rather than resolved paths.

Common situations: A malicious community package whose name/version contains traversal characters; a registry tarball whose `package.json` name field resolves outside the extraction dir; running the scanner against an untrusted npm spec; TEMP_DIR on a path that includes symlinked components.

Related errors


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