{"record":{"id":"090c8df0ee7482e5","repo":"danny-avila/LibreChat","slug":"invalid-file-path-090c8d","errorCode":null,"errorMessage":"Invalid file path","messagePattern":"Invalid file path","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"api/server/services/Files/Local/crud.js","lineNumber":267,"sourceCode":"    const rel = path.relative(userUploadDir, filepath);\n    if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {\n      throw new Error(`Invalid file path: ${cleanFilepath}`);\n    }\n\n    await unlinkFile(filepath);\n    return;\n  }\n\n  const parts = cleanFilepath.split(path.sep);\n  const subfolder = parts[1];\n  if (!subfolder && parts[0] === EModelEndpoint.agents) {\n    logger.warn(`Agent File ${file.file_id} is missing filepath, may have been deleted already`);\n    return;\n  }\n  const filepath = path.join(publicPath, cleanFilepath);\n\n  if (!isValidPath(req, publicPath, subfolder, filepath)) {\n    throw new Error('Invalid file path');\n  }\n\n  await unlinkFile(filepath);\n};\n\n/**\n * Uploads a file to the specified upload directory.\n *\n * @param {Object} params - The params object.\n * @param {ServerRequest} params.req - The request object from Express. It should have a `user` property with an `id` representing the user\n * @param {Express.Multer.File} params.file - The file object, which is part of the request. The file object should\n *                                     have a `path` property that points to the location of the uploaded file.\n * @param {string} params.file_id - The file ID.\n *\n * @returns {Promise<{ filepath: string, bytes: number }>}\n *          A promise that resolves to an object containing:\n *            - filepath: The path where the file is saved.\n *            - bytes: The size of the file in bytes.","sourceCodeStart":249,"sourceCodeEnd":285,"githubUrl":"https://github.com/danny-avila/LibreChat/blob/5ff282f9006c436e561de1afd39a481bea1ef0d8/api/server/services/Files/Local/crud.js#L249-L285","documentation":"Thrown by deleteLocalFile when the filepath does not match the /uploads/{userId} pattern and isValidPath returns false. isValidPath resolves the filepath against publicPath/{subfolder}/{userId} and checks that the relative path stays inside. This guards non-upload files (e.g., /images/{userId}/...) against path traversal and cross-user deletion. The subfolder is extracted from the filepath's second path segment.","triggerScenarios":"Calling deleteLocalFile(req, file) where file.filepath references a public path (e.g., /images/{userId}/file.png) but the resolved path escapes the expected publicPath/subfolder/userId containment. This fires when isValidPath detects the filepath resolving outside the user's designated directory.","commonSituations":"A file record references a path under a different user's directory, or the subfolder does not match an expected image directory. Or an attacker crafts a filepath that resolves outside containment via traversal or absolute path injection. The subfolder extraction (parts[1]) can also be undefined if the filepath is a bare segment like '/images'.","solutions":["Verify file.filepath in the database is a valid public path under the expected subfolder and user ID.","Ensure the subfolder component matches a known public directory type (e.g., 'images').","Audit the file creation pipeline to confirm filepath is always built as /{subfolder}/{userId}/{safeFilename}.","Handle the error at the controller level and return 403 Forbidden, as this often indicates an authorization boundary violation."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const cleanFilepath = file.filepath.split('?')[0];\nconst parts = cleanFilepath.split(path.sep);\nconst subfolder = parts[1];\nif (!subfolder || !isValidPath(req, publicPath, subfolder, path.join(publicPath, cleanFilepath))) {\n  throw new Error('File path validation failed');\n}","typeGuard":"function isValidLocalFilePath(req, publicPath, filepath) {\n  const clean = filepath.split('?')[0];\n  const parts = clean.split(path.sep);\n  const subfolder = parts[1];\n  if (!subfolder) return false;\n  return isValidPath(req, publicPath, subfolder, path.join(publicPath, clean));\n}","tryCatchPattern":"try {\n  await deleteLocalFile(req, file);\n} catch (error) {\n  if (error.message === 'Invalid file path') {\n    return res.status(403).json({ error: 'File path is not within user scope' });\n  }\n  throw error;\n}","preventionTips":["Ensure all public file paths include both a subfolder and userId component.","Audit file records for paths that don't match the expected /subfolder/userId/filename structure.","Reject file operations where the resolved path escapes the user's designated directory."],"tags":["security","path-traversal","file-deletion","local-storage","authorization"],"backgroundTag":null,"analyzedSha":"5ff282f9006c436e561de1afd39a481bea1ef0d8","analyzedAt":"2026-08-12T21:38:08.145Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}