payloadcms/payload · error · APIError
Invalid filename.
Error message
Invalid filename.
What it means
APIError (HTTP 400) thrown as a path-traversal guard in the local-filesystem fallback of getFileHandler. After resolving staticDir and joining the requested filename, if the resolved path does not start with resolvedDir + path.sep, the filename tried to escape the upload directory (e.g. via '..' segments or an absolute path).
Source
Thrown at packages/payload/src/uploads/endpoints/getFile.ts:73
})
if (customResponse && customResponse instanceof Response) {
break
}
}
if (customResponse instanceof Response) {
return customResponse
}
}
// Local filesystem fallback — cloud storage handlers return a Response above
// and have their own filename validation via sanitizeFilename.
const fileDir = collection.config.upload?.staticDir || collection.config.slug
const resolvedDir = path.resolve(fileDir)
const filePath = path.resolve(resolvedDir, filename)
if (!filePath.startsWith(resolvedDir + path.sep)) {
throw new APIError('Invalid filename.', httpStatus.BAD_REQUEST)
}
let stats: Stats
try {
stats = await fsPromises.stat(filePath)
} catch (err) {
if ((err as { code?: string }).code === 'ENOENT') {
req.payload.logger.error(
`File ${filename} for collection ${collection.config.slug} is missing on the disk. Expected path: ${filePath}`,
)
// Omit going to the routeError handler by returning response instead of
// throwing an error to cut down log noise. The response still matches what you get with APIError to not leak details to the user.
return Response.json(
{
errors: [
{View on GitHub (pinned to 00c58b35c0)
Solutions
- Sanitize filenames on the client/request layer (drop '/', '\', '..') before constructing the URL.
- Ensure cloud-storage upload handlers return a Response so execution never reaches the filesystem traversal branch.
- Confirm staticDir is a stable absolute base with no symlinks escaping it.
- If serving custom filenames, route them through sanitizeFilename (used elsewhere in Payload) before file lookups.
Example fix
// client — before
const url = `/api/media/file/${userInput}`
// after — strip path separators and traversal
const safeName = userInput.replace(/[/\\]|\.\./g, '')
const url = `/api/media/file/${encodeURIComponent(safeName)}` Defensive patterns
Strategy: validation
Validate before calling
function isSafeFilename(name: string): boolean {
// reject traversal, separators, absolute, empty
return /^[^/\\\u0000]+$/.test(name) && !name.includes('..') && name.trim().length > 0
}
if (!isSafeFilename(filename)) throw new Error('Invalid filename') Type guard
const isSanitizedFilename = (name: string): boolean =>
!/[\/\\]|\.\./.test(name) && name.length > 0 && !name.includes('\u0000') Try / catch
try {
await fetch(`/api/media/file/${encodeURIComponent(name)}`)
} catch (e) {
if (/Invalid filename/.test((e as Error).message)) alert('Bad filename')
} Prevention
- Always sanitize user-supplied filenames before putting them in a URL.
- Prefer encodeURIComponent on the filename segment.
- Don't allow symlinks inside staticDir.
- Ensure custom upload handlers return a Response so the FS branch is never reached.
When it happens
Trigger: A GET /api/:collection/file/<filename> request where <filename> contains '../' sequences, a leading slash making it resolve outside staticDir, or NUL/encoded traversal that resolves outside the upload dir. Triggered only on the local-filesystem path (after cloud handlers return nothing).
Common situations: Malicious or buggy client constructing filenames from user input; URL-encoding tricks (%2e%2e%2f); symlink inside staticDir pointing outside; misconfigured staticDir with a trailing component that defeats the startsWith check; a custom upload handler that didn't return a Response letting execution fall through to the filesystem branch.
Related errors
- You are not allowed to perform this action.
- You are not allowed to perform this action.
- Could not read uploaded file for validation.
- File type ${mimeTypeFromExtension} (from extension ${typeFro
- There was an error deleting file.
AI-assisted analysis of payloadcms/payload@00c58b35c0 (2026-08-12).
Data as JSON: /api/errors/aa69cfb8903a1803.
Report an issue: GitHub.