{"record":{"id":"aa69cfb8903a1803","repo":"payloadcms/payload","slug":"invalid-filename","errorCode":null,"errorMessage":"Invalid filename.","messagePattern":"Invalid filename\\.","errorType":"http","errorClass":"APIError","httpStatus":400,"severity":"error","filePath":"packages/payload/src/uploads/endpoints/getFile.ts","lineNumber":73,"sourceCode":"      })\n      if (customResponse && customResponse instanceof Response) {\n        break\n      }\n    }\n\n    if (customResponse instanceof Response) {\n      return customResponse\n    }\n  }\n\n  // Local filesystem fallback — cloud storage handlers return a Response above\n  // and have their own filename validation via sanitizeFilename.\n  const fileDir = collection.config.upload?.staticDir || collection.config.slug\n  const resolvedDir = path.resolve(fileDir)\n  const filePath = path.resolve(resolvedDir, filename)\n\n  if (!filePath.startsWith(resolvedDir + path.sep)) {\n    throw new APIError('Invalid filename.', httpStatus.BAD_REQUEST)\n  }\n\n  let stats: Stats\n\n  try {\n    stats = await fsPromises.stat(filePath)\n  } catch (err) {\n    if ((err as { code?: string }).code === 'ENOENT') {\n      req.payload.logger.error(\n        `File ${filename} for collection ${collection.config.slug} is missing on the disk. Expected path: ${filePath}`,\n      )\n\n      // Omit going to the routeError handler by returning response instead of\n      // throwing an error to cut down log noise. The response still matches what you get with APIError to not leak details to the user.\n      return Response.json(\n        {\n          errors: [\n            {","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/payloadcms/payload/blob/00c58b35c0ed348ddc22daabf467b139727214fd/packages/payload/src/uploads/endpoints/getFile.ts#L55-L91","documentation":"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).","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// client — before\nconst url = `/api/media/file/${userInput}`\n// after — strip path separators and traversal\nconst safeName = userInput.replace(/[/\\\\]|\\.\\./g, '')\nconst url = `/api/media/file/${encodeURIComponent(safeName)}`","handlingStrategy":"validation","validationCode":"function isSafeFilename(name: string): boolean {\n  // reject traversal, separators, absolute, empty\n  return /^[^/\\\\\\u0000]+$/.test(name) && !name.includes('..') && name.trim().length > 0\n}\nif (!isSafeFilename(filename)) throw new Error('Invalid filename')","typeGuard":"const isSanitizedFilename = (name: string): boolean =>\n  !/[\\/\\\\]|\\.\\./.test(name) && name.length > 0 && !name.includes('\\u0000')","tryCatchPattern":"try {\n  await fetch(`/api/media/file/${encodeURIComponent(name)}`)\n} catch (e) {\n  if (/Invalid filename/.test((e as Error).message)) alert('Bad filename')\n}","preventionTips":["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."],"tags":["upload","security","path-traversal","filesystem"],"backgroundTag":null,"analyzedSha":"00c58b35c0ed348ddc22daabf467b139727214fd","analyzedAt":"2026-08-12T20:45:03.758Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}