nocobase/nocobase · error · Error

PATH_TRAVERSAL

PATH_TRAVERSAL

Error message

Invalid storage sub path

What it means

normalizeStoragePathForJoin (in plugin-file-manager utils) validates each path component before joining it into a storage path. It throws the given pathError message ('Invalid storage sub path') when a value is not a string, contains a NUL byte, or — unless allowLeadingSlash is set — begins with a leading '/'. This prevents absolute paths and injection characters from entering storage path joins.

Source

Thrown at packages/plugins/@nocobase/plugin-file-manager/src/server/utils.ts:199

    const id = extname && fileIdSegment.endsWith(extname) ? fileIdSegment.slice(0, -extname.length) : fileIdSegment;
    return decodeURIComponent(segments[filesIndex + 1]) === appName && id === String(file.id);
  } catch (error) {
    return false;
  }
}

function pathError(message: string) {
  const error = new Error(message) as NodeJS.ErrnoException;
  error.code = 'PATH_TRAVERSAL';
  return error;
}

function normalizeStoragePathForJoin(value: unknown, message: string, { allowLeadingSlash = false } = {}) {
  if (value == null || value === '') {
    return '';
  }
  if (typeof value !== 'string' || value.includes('\0')) {
    throw pathError(message);
  }
  const normalized = value.replace(/\\/g, '/');
  if (!allowLeadingSlash && normalized.startsWith('/')) {
    throw pathError(message);
  }
  const segments = normalized
    .replace(/^\/+|\/+$/g, '')
    .split('/')
    .filter((segment) => segment && segment !== '.');
  if (segments.some((segment) => segment === '..')) {
    throw pathError('Access denied');
  }
  return segments.join('/');
}

export function normalizeStorageSubPath(subPath?: unknown) {
  return normalizeStoragePathForJoin(subPath, 'Invalid storage sub path');
}

View on GitHub (pinned to fa42722fef)

Solutions

  1. Remove the leading '/' from the storage sub path — it must be relative.
  2. Strip NUL bytes and control characters from the path before use.
  3. Ensure the value passed is a plain string, not an object/array from query parsing.
  4. Fix the storage record's `path` option in the file-manager settings.

Example fix

// before
storage.path = '/uploads/reports'; // leading slash rejected
// after
storage.path = 'uploads/reports';
Defensive patterns

Strategy: validation

Validate before calling

function assertSubPath(v: unknown) {
  if (v == null || v === '') return;
  if (typeof v !== 'string' || v.includes('\0')) throw new Error('Invalid storage sub path');
  const norm = v.replace(/\\/g, '/');
  if (norm.startsWith('/')) throw new Error('Invalid storage sub path: must be relative');
}
assertSubPath(storage.path);

Type guard

function isRelativeSubPath(v: unknown): v is string {
  return typeof v === 'string' && !v.includes('\0') && !v.replace(/\\/g, '/').startsWith('/');
}

Try / catch

try {
  const p = normalizeStorageSubPath(raw);
} catch (e) {
  if (e.code === 'PATH_TRAVERSAL') return res.status(400).json({ error: 'sub path must be a relative, NUL-free string' });
  throw e;
}

Prevention

When it happens

Trigger: Calling normalizeStorageSubPath / normalizedStoragePath with a sub path that is a non-string, contains '\0', or starts with '/' — e.g. storage.path configured as '/uploads' or a client sending an absolute path in a sub-path field.

Common situations: Storage records imported from other systems with leading-slash paths; template strings accidentally prefixing '/'; NUL-byte injection attempts on the path parameter.

Related errors


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