n8n-io/n8n · error · Error

Invalid package specification

Error message

Invalid package specification

What it means

Thrown by resolvePackage when packageSpec fails the regex /^[a-zA-Z0-9@/_.-]+$/. The check is a command-injection guard: packageSpec is later interpolated into spawnSync('npm', ['-q', 'pack', `${packageName}@${version}`]) and must never carry shell metacharacters or whitespace.

Source

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

 *
 * @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('@');
			return {
				packageName: packageSpec.substring(0, lastAtIndex),
				version: packageSpec.substring(lastAtIndex + 1),
			};
		} else {
			// Handle scoped packages without version
			return { packageName: packageSpec, version: null };
		}
	}
	// Handle regular packages
	const parts = packageSpec.split('@');

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Pass only a plain registry package spec: 'name', '@scope/name', 'name@version', or '@scope/name@version'.
  2. If you need a git/url source, fetch and resolve it outside resolvePackage and feed the resulting name@version in.
  3. Strip leading/trailing whitespace from user input before calling resolvePackage.
  4. If a legitimate character is rejected, extend the allowlist regex deliberately rather than widening it broadly.

Example fix

// before
resolvePackage(userInput); // userInput = 'my pkg#' -> throws

// after - normalize then validate
const spec = String(userInput ?? '').trim();
if (!/^[a-zA-Z0-9@/_.-]+$/.test(spec)) {
  throw new Error(`Unsupported package spec; use name@version form: ${JSON.stringify(spec)}`);
}
const { packageName, version } = resolvePackage(spec);
Defensive patterns

Strategy: validation

Validate before calling

const PACKAGE_SPEC_RE = /^[a-zA-Z0-9@/_.-]+$/;

function isValidPackageSpec(spec: unknown): spec is string {
  return typeof spec === 'string' && PACKAGE_SPEC_RE.test(spec) && spec.trim().length > 0;
}

if (!isValidPackageSpec(userInput)) {
  throw new Error('Package spec must be name, @scope/name, name@version, or @scope/name@version');
}

Type guard

function isRegistrySpec(spec: string): boolean {
  // Reject git/url specs up front - they legitimately fail the regex but for a different reason.
  return !spec.startsWith('git+') && !spec.startsWith('file:') && !spec.startsWith('http') && /^[a-zA-Z0-9@/_.-]+$/.test(spec);
}

Prevention

When it happens

Trigger: Calling resolvePackage with a spec containing spaces, quotes, semicolons, ampersands, pipe characters, backticks, '$', parentheses, or any non-ASCII character. Also rejects specs with backslashes or colons.

Common situations: Passing a git URL or tarball URL instead of a registry name (e.g. 'git+https://...'); a local file path spec ('./my-package'); a spec copied from a shell command that includes flags ('pkg --registry=...'); a typo introducing a disallowed character.

Related errors


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