{"record":{"id":"0e2ebd2e9e5a8b5e","repo":"FlowiseAI/Flowise","slug":"mime-type-mismatch-file-extension-normalizedex","errorCode":null,"errorMessage":"MIME type mismatch: file extension \"${normalizedExt}\" does not match declared MIME type \"${mimetype}\". Expected: ${expectedExt}","messagePattern":"MIME type mismatch: file extension \"(.+?)\" does not match declared MIME type \"(.+?)\"\\. Expected: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/components/src/validator.ts","lineNumber":166,"sourceCode":"\n    const normalizedExt = extractFileExtension(filename)\n\n    if (!normalizedExt) {\n        // Files without extensions are rejected for security\n        throw new Error('File type not allowed: files must have a valid file extension')\n    }\n\n    // Get the expected extension from mapMimeTypeToExt (returns extension without dot)\n    const expectedExt = mapMimeTypeToExt(mimetype)\n\n    if (!expectedExt) {\n        // If mapMimeTypeToExt doesn't recognize the MIME type, it's not supported\n        throw new Error(`MIME type \"${mimetype}\" is not supported or does not have a valid file extension mapping`)\n    }\n\n    // Ensure the file extension matches the expected extension for the MIME type\n    if (normalizedExt !== expectedExt) {\n        throw new Error(\n            `MIME type mismatch: file extension \"${normalizedExt}\" does not match declared MIME type \"${mimetype}\". Expected: ${expectedExt}`\n        )\n    }\n}\n\n/**\n * Filters an array of MIME type strings to only those allowed for file upload config.\n * Used when sanitizing chatbotConfig.allowedUploadFileTypes to prevent malicious values.\n * @param {string[]} mimeTypes Raw MIME types (e.g. from splitting comma-separated config)\n * @returns {string[]} Only MIME types that pass isAllowedUploadMimeType\n */\nexport const filterAllowedUploadMimeTypes = (mimeTypes: string[]): string[] => {\n    if (!Array.isArray(mimeTypes)) return []\n    return mimeTypes.map((m) => (typeof m === 'string' ? m.trim() : '')).filter((m) => m !== '' && isAllowedUploadMimeType(m))\n}\n\n/**\n * Get allowed base directories for vector store operations","sourceCodeStart":148,"sourceCodeEnd":184,"githubUrl":"https://github.com/FlowiseAI/Flowise/blob/abe4a8601a058047b350c260676826e21dd14101/packages/components/src/validator.ts#L148-L184","documentation":"Thrown when the file's actual extension (normalizedExt) differs from the extension that mapMimeTypeToExt expects for the declared MIME type. This is the core CVE-2025-61687 spoofing block: e.g. a file named payload.js declaring image/png is rejected because png maps to 'png', not 'js'.","triggerScenarios":"Attacker uploads evil.js with Content-Type image/png to bypass an extension allow-list; client genuinely mislabels the type (renamed a .jpg to .png but kept image/jpeg); normalisation gap (extension 'jpg' vs expected 'jpg' should pass, but a real mismatch like 'txt' vs 'pdf' fails).","commonSituations":"Security control catching genuine spoofing attempts; benign mismatch from users renaming files; extension-alias edge cases not covered by the normalization map in extractFileExtension.","solutions":["Confirm the filename extension matches the actual file content and re-upload with the correct pair.","If the mismatch is benign (alias), extend extractFileExtension's normalization map (e.g. htm→html) so legitimate pairs pass.","For genuine spoofing, block and log the attempt; do not relax the check globally.","Ensure clients derive MIME from real content (e.g. file-type sniffing) rather than hardcoding."],"exampleFix":"// before\nif (normalizedExt !== expectedExt) {\n  throw new Error(`MIME type mismatch: file extension \"${normalizedExt}\" does not match declared MIME type \"${mimetype}\". Expected: ${expectedExt}`)\n}\n\n// after — surface a clear 415 at the handler and keep validator strict\ntry {\n  validateMimeTypeAndExtensionMatch(req.file.originalname, req.file.mimetype)\n} catch (e) {\n  return res.status(415).json({ error: e.message, filename: req.file.originalname, declaredMime: req.file.mimetype })\n}","handlingStrategy":"validation","validationCode":"import { mapMimeTypeToExt } from './utils'\nfunction extensionMatchesMime(filename: string, mime: string): boolean {\n  const parts = filename.split('.')\n  const ext = (parts[parts.length - 1] || '').toLowerCase()\n  const expected = mapMimeTypeToExt(mime.trim().toLowerCase())\n  return Boolean(expected) && ext === expected\n}","typeGuard":"function isConsistentFileMeta(filename: string, mime: string): boolean {\n  const ext = (filename.split('.').pop() || '').toLowerCase()\n  const expected = mapMimeTypeToExt(mime.trim().toLowerCase())\n  return Boolean(expected) && ext === expected\n}","tryCatchPattern":"if (!isConsistentFileMeta(filename, mimetype)) {\n  throw new Error(`MIME type mismatch: file extension \"${normalizedExt}\" does not match declared MIME type \"${mimetype}\". Expected: ${expectedExt}`)\n}","preventionTips":["Have clients derive MIME from real content (file-type sniffing) instead of hardcoding.","Extend extractFileExtension's alias map for legitimate extension variants (htm→html, jpeg→jpg).","Treat genuine mismatches as security events; log and block, never relax globally."],"tags":["security","cve","mime","file-upload","spoofing"],"backgroundTag":null,"analyzedSha":"abe4a8601a058047b350c260676826e21dd14101","analyzedAt":"2026-08-12T16:04:40.823Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}