{"record":{"id":"06288364fc3fbc7e","repo":"Budibase/budibase","slug":"file-filename-has-an-invalid-extension-ex","errorCode":null,"errorMessage":"File \"${fileName}\" has an invalid extension: \"${extension}\"","messagePattern":"File \"(.+?)\" has an invalid extension: \"(.+?)\"","errorType":"http","errorClass":"BadRequestError","httpStatus":400,"severity":"error","filePath":"packages/server/src/api/controllers/static/index.ts","lineNumber":288,"sourceCode":"      }\n\n      const extension = [...fileName.split(\".\")].pop()\n      if (!extension) {\n        throw new BadRequestError(\n          `File \"${fileName}\" has no extension, an extension is required to upload a file`\n        )\n      }\n\n      const extensionLower = extension.toLowerCase()\n      const isPublicUser =\n        ctx.roleId === roles.BUILTIN_ROLE_IDS.PUBLIC ||\n        ctx.user?.roleId === roles.BUILTIN_ROLE_IDS.PUBLIC\n      const enforceInvalidExtension = isPublicUser || !env.SELF_HOSTED\n      if (\n        enforceInvalidExtension &&\n        InvalidFileExtensions.includes(extensionLower)\n      ) {\n        throw new BadRequestError(\n          `File \"${fileName}\" has an invalid extension: \"${extension}\"`\n        )\n      }\n\n      if (ACTIVE_CONTENT_EXTENSIONS.has(extensionLower)) {\n        throw new ActiveContentFileError(fileName)\n      }\n\n      const mimeType =\n        typeof rawMimeType === \"string\" ? rawMimeType.toLowerCase() : undefined\n      if (\n        mimeType &&\n        ACTIVE_CONTENT_MIME_TYPES.some(type => mimeType.includes(type))\n      ) {\n        throw new ActiveContentFileError(fileName)\n      }\n\n      if (","sourceCodeStart":270,"sourceCodeEnd":306,"githubUrl":"https://github.com/Budibase/budibase/blob/a81a902e9a8fe55b467d106765f6638f12e35c49/packages/server/src/api/controllers/static/index.ts#L270-L306","documentation":"Thrown by uploadFile when the extension (lowercased) is in the InvalidFileExtensions blocklist from @budibase/shared-core. The check is enforced for public (unauthenticated) users and on non-self-hosted (cloud) deployments; self-hosted authenticated admins bypass it.","triggerScenarios":"Uploading a file with a blacklisted extension (e.g. .exe, .bat, .sh) when the requester's roleId is PUBLIC or env.SELF_HOSTED is false.","commonSituations":"Distributing installers/scripts via Budibase attachments in cloud apps, or self-hosters confused because the same file works locally but is blocked in cloud.","solutions":["Rename or repackage the file with an allowed extension (e.g. zip it first)","Host the file externally and link to it instead of uploading","For self-hosted environments where this is intended, ensure env.SELF_HOSTED=true and upload as an authenticated non-public user"],"exampleFix":"// before\nupload 'installer.exe' as anonymous user in cloud\n// after\nzip to 'installer.zip' or host externally and link","handlingStrategy":"validation","validationCode":"const BLOCKED = ['exe','bat','sh','cmd','com','scr','ps1'] // subset of InvalidFileExtensions\nfunction isExtensionAllowed(name) {\n  const ext = (name.split('.').pop() || '').toLowerCase()\n  return !BLOCKED.includes(ext)\n}","typeGuard":"const isSafeExtension = (name: string): boolean =>\n  !InvalidFileExtensions.includes((name.split('.').pop() || '').toLowerCase())","tryCatchPattern":"try {\n  await api.uploadFile(fd)\n} catch (err) {\n  if (err instanceof BadRequestError && err.message.includes('invalid extension')) {\n    // package the file (e.g. zip) or host externally\n  } else throw err\n}","preventionTips":["Check extensions against the blocklist before uploading, especially as an anonymous user in cloud","Zip binaries/scripts when distribution via attachments is required","Remember self-hosted authenticated uploads are not subject to this filter"],"tags":["security","upload","file-extension","validation"],"backgroundTag":"blocked-file-extension","analyzedSha":"a81a902e9a8fe55b467d106765f6638f12e35c49","analyzedAt":"2026-08-29T01:03:10.972Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}