{"record":{"id":"4f98eb6c251ff6ce","repo":"danny-avila/LibreChat","slug":"path-traversal-detected-in-filename","errorCode":null,"errorMessage":"Path traversal detected in filename","messagePattern":"Path traversal detected in filename","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"api/server/services/Files/Local/crud.js","lineNumber":91,"sourceCode":"  try {\n    const { publicPath, uploads } = paths;\n\n    /**\n     * For 'images': save to publicPath/images/userId (images are served statically)\n     * For 'uploads': save to uploads/userId (files downloaded via API)\n     * */\n    const directoryPath =\n      basePath === 'images' ? path.join(publicPath, basePath, userId) : path.join(uploads, userId);\n\n    if (!fs.existsSync(directoryPath)) {\n      fs.mkdirSync(directoryPath, { recursive: true });\n    }\n\n    const resolvedDir = path.resolve(directoryPath);\n    const resolvedPath = path.resolve(resolvedDir, fileName);\n    const rel = path.relative(resolvedDir, resolvedPath);\n    if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {\n      throw new Error('Path traversal detected in filename');\n    }\n    fs.writeFileSync(resolvedPath, buffer);\n\n    const filePath = path.posix.join('/', basePath, userId, fileName);\n\n    return filePath;\n  } catch (error) {\n    logger.error('[saveLocalBuffer] Error while saving the buffer:', error);\n    throw error;\n  }\n}\n\n/**\n * Saves a file from a given URL to a local directory. The function fetches the file using the provided URL,\n * determines the content type, and saves it to a specified local directory with the correct file extension.\n * If the specified directory does not exist, it is created. The function returns the name of the saved file\n * or null in case of an error.\n *","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/server/services/Files/Local/crud.js#L73-L109","documentation":"Thrown by saveLocalBuffer when the resolved file path escapes the intended target directory. The function resolves the directory (publicPath/images/userId or uploads/userId), joins the fileName, then checks via path.relative whether the result stays inside the directory. If the relative path starts with '..', is absolute, or contains a parent-directory separator, the write is refused. This prevents path traversal attacks where a crafted fileName like '../../etc/passwd' would write outside the user's directory.","triggerScenarios":"Calling saveLocalBuffer({ userId, buffer, fileName }) where fileName contains path traversal sequences such as '../', an absolute path like '/etc/cron.d/evil', or uses backslash-based traversal on Windows. Any fileName that, when resolved against the target directory, points outside it triggers this error.","commonSituations":"User-supplied filenames are passed directly as fileName without sanitization. An attacker uploads a file with a malicious original name containing traversal sequences. Or a bug in upstream code constructs fileName from untrusted path components without stripping directory separators.","solutions":["Sanitize fileName before calling saveLocalBuffer: strip directory components with path.basename(fileName) and reject any remaining path separators.","Generate fileName server-side from a UUID or file_id rather than trusting user-supplied names.","Validate that fileName matches a safe pattern (e.g., /^[a-zA-Z0-9._-]+$/) before processing.","Audit the upload pipeline to ensure fileName is always derived from sanitized or server-generated values."],"exampleFix":"// before\nawait saveLocalBuffer({ userId, buffer, fileName: file.originalname });\n\n// after — sanitize filename\nconst safeName = path.basename(file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');\nawait saveLocalBuffer({ userId, buffer, fileName: safeName });","handlingStrategy":"validation","validationCode":"const safeName = path.basename(fileName).replace(/[^a-zA-Z0-9._-]/g, '_');\nif (safeName !== fileName) {\n  throw new Error(`Filename contains invalid characters; sanitized to ${safeName}`);\n}","typeGuard":"function isSafeFilename(name: string): boolean {\n  return /^[a-zA-Z0-9._-]+$/.test(name) && !name.includes('..');\n}","tryCatchPattern":"try {\n  const filePath = await saveLocalBuffer({ userId, buffer, fileName });\n} catch (error) {\n  if (error.message.includes('Path traversal')) {\n    // reject the upload — potential attack\n    return res.status(400).json({ error: 'Invalid filename' });\n  }\n  throw error;\n}","preventionTips":["Always sanitize filenames with path.basename() before passing to file operations.","Generate filenames server-side from file_id or UUID rather than trusting user-supplied names.","Reject filenames containing path separators, '..', or non-printable characters at the API boundary.","Run security audits that test path traversal payloads against file upload endpoints."],"tags":["security","path-traversal","file-upload","local-storage","input-validation"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}