{"record":{"id":"40c3edc472340637","repo":"nocobase/nocobase","slug":"path-traversal-40c3ed","errorCode":"PATH_TRAVERSAL","errorMessage":"Invalid storage sub path","messagePattern":"Invalid storage sub path","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/plugins/@nocobase/plugin-file-manager/src/server/utils.ts","lineNumber":199,"sourceCode":"    const id = extname && fileIdSegment.endsWith(extname) ? fileIdSegment.slice(0, -extname.length) : fileIdSegment;\n    return decodeURIComponent(segments[filesIndex + 1]) === appName && id === String(file.id);\n  } catch (error) {\n    return false;\n  }\n}\n\nfunction pathError(message: string) {\n  const error = new Error(message) as NodeJS.ErrnoException;\n  error.code = 'PATH_TRAVERSAL';\n  return error;\n}\n\nfunction normalizeStoragePathForJoin(value: unknown, message: string, { allowLeadingSlash = false } = {}) {\n  if (value == null || value === '') {\n    return '';\n  }\n  if (typeof value !== 'string' || value.includes('\\0')) {\n    throw pathError(message);\n  }\n  const normalized = value.replace(/\\\\/g, '/');\n  if (!allowLeadingSlash && normalized.startsWith('/')) {\n    throw pathError(message);\n  }\n  const segments = normalized\n    .replace(/^\\/+|\\/+$/g, '')\n    .split('/')\n    .filter((segment) => segment && segment !== '.');\n  if (segments.some((segment) => segment === '..')) {\n    throw pathError('Access denied');\n  }\n  return segments.join('/');\n}\n\nexport function normalizeStorageSubPath(subPath?: unknown) {\n  return normalizeStoragePathForJoin(subPath, 'Invalid storage sub path');\n}","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/nocobase/nocobase/blob/fa42722fefe44265490dff2c27d79e2882bce4fa/packages/plugins/@nocobase/plugin-file-manager/src/server/utils.ts#L181-L217","documentation":"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.","triggerScenarios":"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.","commonSituations":"Storage records imported from other systems with leading-slash paths; template strings accidentally prefixing '/'; NUL-byte injection attempts on the path parameter.","solutions":["Remove the leading '/' from the storage sub path — it must be relative.","Strip NUL bytes and control characters from the path before use.","Ensure the value passed is a plain string, not an object/array from query parsing.","Fix the storage record's `path` option in the file-manager settings."],"exampleFix":"// before\nstorage.path = '/uploads/reports'; // leading slash rejected\n// after\nstorage.path = 'uploads/reports';","handlingStrategy":"validation","validationCode":"function assertSubPath(v: unknown) {\n  if (v == null || v === '') return;\n  if (typeof v !== 'string' || v.includes('\\0')) throw new Error('Invalid storage sub path');\n  const norm = v.replace(/\\\\/g, '/');\n  if (norm.startsWith('/')) throw new Error('Invalid storage sub path: must be relative');\n}\nassertSubPath(storage.path);","typeGuard":"function isRelativeSubPath(v: unknown): v is string {\n  return typeof v === 'string' && !v.includes('\\0') && !v.replace(/\\\\/g, '/').startsWith('/');\n}","tryCatchPattern":"try {\n  const p = normalizeStorageSubPath(raw);\n} catch (e) {\n  if (e.code === 'PATH_TRAVERSAL') return res.status(400).json({ error: 'sub path must be a relative, NUL-free string' });\n  throw e;\n}","preventionTips":["Store storage sub paths as relative segments without leading slashes.","Sanitize imported/migrated storage records for '\\0' and leading '/' before saving.","Treat sub-path validation failures from clients as suspicious input and log them."],"tags":["path-traversal","file-manager","validation"],"backgroundTag":"invalid-storage-path","analyzedSha":"fa42722fefe44265490dff2c27d79e2882bce4fa","analyzedAt":"2026-09-01T00:54:31.202Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}