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
- Remove any '..' or absolute-path components from the child argument; pass a bare package name.
- Verify the relevant env vars (NODE_MODULES_PATH, plugin storage path, PLUGIN_PATH) point to the intended real directories (check with path.resolve/realpath).
- Log resolvedBase and resolvedTarget to see exactly how the escape occurs.
- 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
- Only pass bare package names (no '..', no absolute paths) into storage path helpers
- Keep NODE_MODULES_PATH / storage env vars pointing at real directories verified with fs.realpath
- Treat this error as a security signal: log and audit the source of the offending input
- Never bypass resolveSafeChildPath with raw path.join for plugin storage
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
- Imported archive package name "${packageName}" resolves outs
- Failed to remove ${label} at "${target}". The current user c
- translateCli('commands.install.messages.skipDownloadLocalApp
- Failed to parse ${sourceLabel}.
- Missing version in ${sourceLabel}.
AI-assisted analysis of nocobase/nocobase@fa42722fef (2026-09-01).
Data as JSON: /api/errors/db70d34545b04f9b.
Report an issue: GitHub.