{"record":{"id":"0982b06ed069e61c","repo":"actualbudget/actual","slug":"invalid-upload-filename","errorCode":null,"errorMessage":"Invalid upload filename","messagePattern":"Invalid upload filename","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/loot-core/src/server/budgetfiles/app.ts","lineNumber":685,"sourceCode":"  app.events.emit('load-budget', { id });\n\n  return {};\n}\n\nasync function uploadFileWeb({\n  filename,\n  contents,\n}: {\n  filename: string;\n  contents: ArrayBuffer;\n}) {\n  if (!Platform.isBrowser) {\n    return null;\n  }\n\n  const safeName = filename.split(/[/\\\\]/).pop()?.replaceAll('\\0', '');\n  if (!safeName || safeName === '.' || safeName === '..') {\n    throw new Error('Invalid upload filename');\n  }\n  await fs.writeFile(fs.join('/uploads', safeName), contents);\n  return {};\n}\n\nasync function getBackups({ id }) {\n  return getAvailableBackups(id);\n}\n\nasync function loadBackup({ id, backupId }) {\n  await _loadBackup(id, backupId);\n}\n\nasync function makeBackup({ id }) {\n  await _makeBackup(id);\n}\n\nasync function getLastOpenedBackup() {","sourceCodeStart":667,"sourceCodeEnd":703,"githubUrl":"https://github.com/actualbudget/actual/blob/d4334cb6e6123f4d3bcea1ad6166608884c7e658/packages/loot-core/src/server/budgetfiles/app.ts#L667-L703","documentation":"uploadFileWeb sanitizes an uploaded filename before writing it into the browser virtual /uploads directory. If after stripping path components and NUL bytes the name is empty, '.', or '..', the write is refused with 'Invalid upload filename' to prevent path traversal or writing to the directory itself.","triggerScenarios":"Calling uploadFileWeb (via the upload IPC handler from a browser file picker) with a filename that is empty, just '.', or just '..' — e.g. passing a raw directory path whose basename collapses, or a name consisting only of separators like '/' or '\\\\'.","commonSituations":"Users drag-and-drop a folder instead of a file; an integration passes an absolute path and takes the wrong basename; a buggy client sends a zero-length name field; Windows/Unix separator mixups leave only slashes after split.","solutions":["Check the uploaded file's name in the browser before calling the handler and ensure it is a non-empty basename like 'report.xlsx'.","Strip directory components yourself with filename.split(/[/\\\\]/).pop() and validate the result is not '.', '..', or empty.","If a drag-and-drop dropped a directory, use a file input (accepting files only) or enumerate entry.files instead of passing the directory name.","Sanitize with NUL-byte removal and reject reserved names before invoking uploadFileWeb."],"exampleFix":"// before\nawait send('upload-file', { filename: dirEntry.name, contents });\n// after\nconst safeName = dirEntry.name.split(/[/\\\\]/).pop()?.replaceAll('\\0', '');\nif (safeName && safeName !== '.' && safeName !== '..') {\n  await send('upload-file', { filename: safeName, contents });\n}","handlingStrategy":"validation","validationCode":"function isValidUploadName(name) {\n  const safe = String(name).split(/[/\\\\]/).pop()?.replaceAll('\\0', '');\n  return Boolean(safe) && safe !== '.' && safe !== '..';\n}\nif (!isValidUploadName(file.name)) throw new Error('Pick a real file, not a folder');","typeGuard":"function hasSafeName(f: unknown): f is string {\n  return typeof f === 'string' && f.length > 0 && !['.', '..'].includes(f.replaceAll('\\0', ''));\n}","tryCatchPattern":"try {\n  await send('upload-file', { filename: file.name, contents });\n} catch (e) {\n  if (e.message === 'Invalid upload filename') {\n    alert('The selected file has an invalid name. Choose a regular file.');\n  } else throw e;\n}","preventionTips":["Use <input type=\"file\"> which always yields real file basenames.","Reject directories in drag-and-drop handlers via dataTransfer.items kind checks.","Sanitize names (strip path and NULs) in the caller before sending.","Add unit tests covering '', '.', '..', and separator-only names."],"tags":["upload","filename","validation","browser"],"backgroundTag":"invalid-filename","analyzedSha":"d4334cb6e6123f4d3bcea1ad6166608884c7e658","analyzedAt":"2026-08-29T01:02:11.213Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}