{"record":{"id":"437c8db6d49688c1","repo":"antiwork/gumroad","slug":"invalid-file-type","errorCode":null,"errorMessage":"Invalid file type.","messagePattern":"Invalid file type\\.","errorType":"validation","errorClass":"ValidationError","httpStatus":null,"severity":"warning","filePath":"app/javascript/components/ProductEdit/ProductTab/ThumbnailEditor.tsx","lineNumber":32,"sourceCode":"  eager: true,\n  query: \"?url\",\n  import: \"default\",\n});\nconst nativeTypeThumbnails = Object.fromEntries(\n  Object.entries(rawThumbnails).map(([key, value]) => [`./${key.split(\"/\").pop()}`, value]),\n);\n\nconst MIN_SIDE_DIMENSION = 600;\nconst MEGABYTE = 1024 * 1024;\nconst MAX_FILE_SIZE = 5 * MEGABYTE;\nexport class ValidationError extends Error {\n  constructor(message = \"Invalid file type.\") {\n    super(message);\n  }\n}\n\nconst validateFile = async (file: File) => {\n  if (!FileUtils.isFileNameExtensionAllowed(file.name, ALLOWED_EXTENSIONS)) throw new ValidationError();\n\n  if (file.size > MAX_FILE_SIZE)\n    throw new ValidationError(\"Could not process your thumbnail, please upload an image with size smaller than 5 MB.\");\n\n  const dimensions = await getImageDimensionsFromFile(file).catch(() => null);\n  if (!dimensions) throw new ValidationError();\n  if (dimensions.height !== dimensions.width) throw new ValidationError(\"Image must be square.\");\n\n  if (dimensions.height < MIN_SIDE_DIMENSION) throw new ValidationError(\"Image must be at least 600x600px.\");\n};\n\nexport const coverUrlForThumbnail = (covers: AssetPreview[]) =>\n  covers.find((cover) => cover.type === \"image\" || cover.type === \"unsplash\")?.url ?? null;\n\nexport const ThumbnailEditor = ({\n  covers,\n  thumbnail,\n  setThumbnail,","sourceCodeStart":14,"sourceCodeEnd":50,"githubUrl":"https://github.com/antiwork/gumroad/blob/afeacbd394069a1cbf0c6c50ee8e900925050370/app/javascript/components/ProductEdit/ProductTab/ThumbnailEditor.tsx#L14-L50","documentation":"First check in validateFile for product thumbnails: FileUtils.isFileNameExtensionAllowed(file.name, ALLOWED_EXTENSIONS) rejects any file whose extension is not on the allowlist, throwing ValidationError with its default message 'Invalid file type.'. This is pure client-side gating before any upload — the file is judged by name extension only, not content.","triggerScenarios":"Uploading a thumbnail with an extension outside ALLOWED_EXTENSIONS (e.g. .tiff, .heic, .bmp, .pdf when only jpg/png/webp-style entries are allowed); double extensions like photo.jpg.exe; an extensionless filename from a screenshot or drag-and-drop tool.","commonSituations":"Designers exporting TIFF/HEIC from cameras and design tools; files saved without extensions; users renaming files to force acceptance (caught here); allowlist updated server-side but stale in the client bundle.","solutions":["Check ALLOWED_EXTENSIONS in ThumbnailEditor.tsx and export/convert the image to one of them (JPEG/PNG/WebP are the usual set).","Re-save via an image editor or `sips`/ImageMagick: `magick input.tiff output.jpg`.","If a legitimate format is being rejected, extend ALLOWED_EXTENSIONS and mirror any server-side validation.","Set the file picker's accept attribute to the allowed MIME types so invalid files cannot be chosen in the first place."],"exampleFix":"// before\n<input type=\"file\" onChange={handleFile} />\n\n// after — the picker itself prevents picking a disallowed type\n<input type=\"file\" accept=\"image/jpeg,image/png,image/webp\" onChange={handleFile} />","handlingStrategy":"validation","validationCode":"const hasAllowedExtension = (file: File): boolean =>\n  FileUtils.isFileNameExtensionAllowed(file.name, ALLOWED_EXTENSIONS);\n\nif (!hasAllowedExtension(file)) {\n  showAlert(`Supported formats: ${ALLOWED_EXTENSIONS.join(', ')}.`, 'error');\n  return;\n}","typeGuard":"const isValidationError = (e: unknown): e is ValidationError => e instanceof ValidationError;","tryCatchPattern":"try {\n  await validateFile(file);\n} catch (e) {\n  if (e instanceof ValidationError) { showAlert(e.message, 'error'); return; } // user-input problem\n  throw e; // unexpected — let it propagate\n}","preventionTips":["Set accept on the file input to the allowed MIME types so the OS picker filters before selection.","Show the allowed extension list in the UI next to the picker — users should not discover the list by failing.","Judge by extension AND content: renamed files pass the extension check but fail decode later.","Keep the client ALLOWED_EXTENSIONS in sync with any server-side validation so both gates agree."],"tags":["file-validation","thumbnail","extension","client-side"],"backgroundTag":"invalid-file-extension","analyzedSha":"afeacbd394069a1cbf0c6c50ee8e900925050370","analyzedAt":"2026-08-21T17:58:52.159Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}