nocobase/nocobase · critical

Path traversal detected

Error message

Path traversal detected

What it means

resolveSafeChildPath resolves baseDir + child and verifies the result stays inside baseDir. If the resolved target escapes the base (via '..' segments, absolute child, or symlinks resolving outside), it throws 'Path traversal detected'. getStoragePluginDir and getNodeModulesPluginDir use it to sandbox plugin package names.

Source

Thrown at packages/core/server/src/plugin-manager/utils.ts:71

    throw new Error('Invalid plugin package name');
  }

  if (packageName.includes('..') || packageName.includes('\\')) {
    throw new Error('Invalid plugin package name');
  }

  const valid = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i.test(packageName);
  if (!valid) {
    throw new Error('Invalid plugin package name');
  }
}

export function resolveSafeChildPath(baseDir: string, child: string) {
  const resolvedBase = path.resolve(baseDir);
  const resolvedTarget = path.resolve(baseDir, child);

  if (resolvedTarget !== resolvedBase && !resolvedTarget.startsWith(`${resolvedBase}${path.sep}`)) {
    throw new Error('Path traversal detected');
  }

  return resolvedTarget;
}

export function getLocalPluginPackagesPathArr(): string[] {
  const pluginPackagesPathArr = process.env.PLUGIN_PATH || DEFAULT_PLUGIN_PATH;
  return pluginPackagesPathArr.split(',').map((pluginPackagesPath) => {
    pluginPackagesPath = pluginPackagesPath.trim();
    return path.isAbsolute(pluginPackagesPath) ? pluginPackagesPath : path.join(process.cwd(), pluginPackagesPath);
  });
}

export function getStoragePluginDir(packageName: string) {
  const pluginStoragePath = resolvePluginStoragePath();
  assertSafePluginPackageName(packageName);
  return resolveSafeChildPath(pluginStoragePath, packageName);
}

View on GitHub (pinned to fa42722fef)

Solutions

  1. Remove any '..' or absolute-path components from the child argument; pass a bare package name.
  2. Verify the relevant env vars (NODE_MODULES_PATH, plugin storage path, PLUGIN_PATH) point to the intended real directories (check with path.resolve/realpath).
  3. Log resolvedBase and resolvedTarget to see exactly how the escape occurs.
  4. If intentionally referencing another directory, do not use this API — it is a security boundary by design.

Example fix

// before
const dir = getStoragePluginDir('../../etc');
// after
const name = '@my/safe-plugin'; // valid, non-traversing package name
const dir = getStoragePluginDir(name);
Defensive patterns

Strategy: validation

Validate before calling

const path = require('path');
function staysInsideBase(baseDir, child) {
  const base = path.resolve(baseDir);
  const target = path.resolve(baseDir, child);
  return target === base || target.startsWith(base + path.sep);
}
if (!staysInsideBase(storagePath, name)) throw new Error('Child path escapes base: ' + child);

Type guard

function isSafeRelativeName(v: string): boolean {
  return typeof v === 'string' && !v.includes('..') && !v.includes('\\') && !v.includes('\0') && !require('path').isAbsolute(v);
}

Try / catch

try {
  const dir = getStoragePluginDir(name);
} catch (e) {
  if (e.message === 'Path traversal detected') {
    console.error(`Refusing path escape for '${name}'; base=${storagePath}`);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: getStoragePluginDir('..') or getNodeModulesPluginDir('../other') where the resolved path escapes the storage/NODE_MODULES base; an absolute child path pointing outside the base; NODE_MODULES_PATH or the plugin storage root misconfigured such that the resolved path falls outside.

Common situations: Malicious or buggy inputs with '../' sequences; env vars like NODE_MODULES_PATH pointing to unexpected locations so even valid names resolve outside; case-sensitive filesystem mismatches on macOS/Windows; code passing absolute paths as the child argument.

Related errors


AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01). Data as JSON: /api/errors/db70d34545b04f9b. Report an issue: GitHub.