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

  1. Sanitize fileName before calling saveLocalBuffer: strip directory components with path.basename(fileName) and reject any remaining path separators.
  2. Generate fileName server-side from a UUID or file_id rather than trusting user-supplied names.
  3. Validate that fileName matches a safe pattern (e.g., /^[a-zA-Z0-9._-]+$/) before processing.
  4. 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

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


AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12). Data as JSON: /api/errors/4f98eb6c251ff6ce. Report an issue: GitHub.