{"record":{"id":"67509b4162d7c4a4","repo":"gitroomhq/postiz-app","slug":"unsupported-file-type","errorCode":null,"errorMessage":"Unsupported file type.","messagePattern":"Unsupported file type\\.","errorType":"http","errorClass":"HttpException","httpStatus":400,"severity":"error","filePath":"apps/backend/src/public-api/routes/v1/public.integrations.controller.ts","lineNumber":139,"sourceCode":"    }\n    if (!response.ok) {\n      throw new HttpException({ msg: 'Failed to fetch URL' }, 400);\n    }\n\n    // Guard against OOM: bail out before buffering the whole body into memory.\n    // Content-Length may be absent or wrong, so we re-check the real size after\n    // download too. The type isn't known yet (sniffed below), so the pre-check\n    // uses the largest allowed cap (video).\n    const maxDownloadSize = getMaxSize('video/mp4');\n    const declaredSize = Number(response.headers.get('content-length'));\n    if (declaredSize && declaredSize > maxDownloadSize) {\n      throw new HttpException({ msg: 'File is too large.' }, 400);\n    }\n\n    const buffer = Buffer.from(await response.arrayBuffer());\n    const detected = await fileTypeFromBuffer(buffer);\n    if (!detected || !PUBLIC_API_ALLOWED_MIME.has(detected.mime)) {\n      throw new HttpException({ msg: 'Unsupported file type.' }, 400);\n    }\n\n    if (buffer.length > getMaxSize(detected.mime)) {\n      throw new HttpException({ msg: 'File is too large.' }, 400);\n    }\n\n    const mimetype = detected.mime;\n    const ext = detected.ext;\n\n    const getFile = await this.storage.uploadFile({\n      buffer,\n      mimetype,\n      size: buffer.length,\n      path: '',\n      fieldname: '',\n      destination: '',\n      stream: new Readable(),\n      filename: '',","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/gitroomhq/postiz-app/blob/0f1647f7491a217d43eb5ae7a480484bdf0aff3e/apps/backend/src/public-api/routes/v1/public.integrations.controller.ts#L121-L157","documentation":"After downloading, uploadsFromUrl sniffs the file's magic bytes with fileTypeFromBuffer and checks the detected MIME against a whitelist (PUBLIC_API_ALLOWED_MIME). If detection fails or the type isn't whitelisted, it throws 400 'Unsupported file type.' — the extension in the URL is irrelevant, only actual content is trusted.","triggerScenarios":"POST /public/v1/uploads/from-url with a URL serving an SVG, GIF variants, HEIC, text/html (an error page), or any binary whose magic bytes aren't in the whitelist. Also happens when the URL returns an HTML login/error page instead of the media, or when detection fails on truncated/empty bodies.","commonSituations":"Pointing at an HTML page behind auth rather than the asset; unusual formats like HEIC/AVIF/WebP variants not in the allowlist; renamed files (a .jpg that's actually a PDF); empty responses from misconfigured CDNs.","solutions":["Download the file locally and run `file` or a magic-byte checker to confirm its real type","Convert/re-encode the asset to a whitelisted format (standard jpeg/png/mp4 etc.)","If the URL returns an HTML error/login page, fix the link or make the asset directly fetchable (public, no auth wall)","Check PUBLIC_API_ALLOWED_MIME in the codebase for the exact accepted list"],"exampleFix":"// before\n{ \"url\": \"https://example.com/photo.heic\" }\n\n// after\n{ \"url\": \"https://example.com/photo.jpg\" } // re-encoded to a whitelisted format","handlingStrategy":"type-guard","validationCode":"const res = await fetch(url);\nconst buf = Buffer.from(await res.arrayBuffer());\nconst sig = buf.subarray(0, 12).toString('hex');\nconst known = { 'ffd8ff': 'jpeg', '89504e47': 'png', '66747966': 'mp4' /* etc */ };\nif (!Object.keys(known).some(k => sig.startsWith(k))) throw new Error('Not a whitelisted media type');","typeGuard":"import { fileTypeFromBuffer } from 'file-type';\nconst isAllowedMediaType = async (buf: Buffer): Promise<boolean> => {\n  const t = await fileTypeFromBuffer(buf);\n  return !!t && ALLOWED_MIME.has(t.mime); // mirror PUBLIC_API_ALLOWED_MIME\n};","tryCatchPattern":"try {\n  await api.uploadsFromUrl({ url });\n} catch (e) {\n  if (e?.response?.data?.msg === 'Unsupported file type.') {\n    // re-encode the asset and retry; don't trust the file extension\n  }\n}","preventionTips":["Serve real media bytes, not HTML error/login pages","Re-encode HEIC/SVG/obscure formats to jpeg/png/mp4","Remember: magic bytes decide, extensions are ignored"],"tags":["file-type","validation","upload","magic-bytes","public-api"],"backgroundTag":"unsupported-media-type","analyzedSha":"0f1647f7491a217d43eb5ae7a480484bdf0aff3e","analyzedAt":"2026-08-27T12:09:55.020Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}