danny-avila/LibreChat · critical
Path traversal detected in filename
Error message
Path traversal detected in filename
What it means
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.
Source
Thrown at api/server/services/Files/Local/crud.js:91
try {
const { publicPath, uploads } = paths;
/**
* For 'images': save to publicPath/images/userId (images are served statically)
* For 'uploads': save to uploads/userId (files downloaded via API)
* */
const directoryPath =
basePath === 'images' ? path.join(publicPath, basePath, userId) : path.join(uploads, userId);
if (!fs.existsSync(directoryPath)) {
fs.mkdirSync(directoryPath, { recursive: true });
}
const resolvedDir = path.resolve(directoryPath);
const resolvedPath = path.resolve(resolvedDir, fileName);
const rel = path.relative(resolvedDir, resolvedPath);
if (rel.startsWith('..') || path.isAbsolute(rel) || rel.includes(`..${path.sep}`)) {
throw new Error('Path traversal detected in filename');
}
fs.writeFileSync(resolvedPath, buffer);
const filePath = path.posix.join('/', basePath, userId, fileName);
return filePath;
} catch (error) {
logger.error('[saveLocalBuffer] Error while saving the buffer:', error);
throw error;
}
}
/**
* Saves a file from a given URL to a local directory. The function fetches the file using the provided URL,
* determines the content type, and saves it to a specified local directory with the correct file extension.
* If the specified directory does not exist, it is created. The function returns the name of the saved file
* or null in case of an error.
*View on GitHub (pinned to 5ff282f900)
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.
Example fix
// before
await saveLocalBuffer({ userId, buffer, fileName: file.originalname });
// after — sanitize filename
const safeName = path.basename(file.originalname).replace(/[^a-zA-Z0-9._-]/g, '_');
await saveLocalBuffer({ userId, buffer, fileName: safeName }); Defensive patterns
Strategy: validation
Validate before calling
const safeName = path.basename(fileName).replace(/[^a-zA-Z0-9._-]/g, '_');
if (safeName !== fileName) {
throw new Error(`Filename contains invalid characters; sanitized to ${safeName}`);
} Type guard
function isSafeFilename(name: string): boolean {
return /^[a-zA-Z0-9._-]+$/.test(name) && !name.includes('..');
} Try / catch
try {
const filePath = await saveLocalBuffer({ userId, buffer, fileName });
} catch (error) {
if (error.message.includes('Path traversal')) {
// reject the upload — potential attack
return res.status(400).json({ error: 'Invalid filename' });
}
throw error;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Invalid file path
- Remote file response too large: ${buffer.length} bytes
- Invalid file path: ${cleanFilepath}
- Invalid file path: ${filepath}
- File embedding failed. The filetype ${file.mimetype} is not
AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12).
Data as JSON: /api/errors/4f98eb6c251ff6ce.
Report an issue: GitHub.