{"record":{"id":"309a2e8fd9de79c3","repo":"RocketChat/Rocket.Chat","slug":"error-invalid-file-uploaded","errorCode":"error-invalid-file-uploaded","errorMessage":"Invalid file uploaded","messagePattern":"Invalid file uploaded","errorType":"error_code","errorClass":"Meteor.Error","httpStatus":400,"severity":"warning","filePath":"apps/meteor/server/api/v1/omnichannel/sms.ts","lineNumber":40,"sourceCode":"import type { ILivechatMessage } from '../../../lib/omnichannel/localTypes';\nimport { sendMessage } from '../../../lib/omnichannel/messages';\nimport { createRoom } from '../../../lib/omnichannel/rooms';\nimport { settings } from '../../../settings';\n\nconst logger = new Logger('SMS');\n\nconst getUploadFile = async (details: Omit<IUpload, '_id' | '_updatedAt'>, fileUrl: string) => {\n\tconst response = await fetch(fileUrl, {\n\t\tignoreSsrfValidation: false,\n\t\tallowList: settings.get<string>('SSRF_Allowlist'),\n\t});\n\n\tconst content = Buffer.from(await response.arrayBuffer());\n\n\tconst contentSize = content.length;\n\n\tif (response.status !== 200 || contentSize === 0) {\n\t\tthrow new Meteor.Error('error-invalid-file-uploaded', 'Invalid file uploaded');\n\t}\n\n\tconst fileStore = FileUpload.getStore('Uploads');\n\n\treturn fileStore.insert({ ...details, size: contentSize }, content);\n};\n\nconst defineDepartment = async (idOrName?: string) => {\n\tif (!idOrName || idOrName === '') {\n\t\treturn;\n\t}\n\n\tconst department = await LivechatDepartment.findOneByIdOrName(idOrName, { projection: { _id: 1 } });\n\treturn department?._id;\n};\n\nconst defineVisitor = async (smsNumber: string, targetDepartment?: string) => {\n\tconst visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber);","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0/apps/meteor/server/api/v1/omnichannel/sms.ts#L22-L58","documentation":"Thrown inside the inbound SMS webhook (POST /api/v1/livechat/sms-incoming/:service) when the server downloads an MMS/SMS media attachment and the provider responds with a non-200 status or an empty body. The fetch goes through Rocket.Chat's serverFetch with SSRF protection, so a host rejected by the SSRF_Allowlist setting also surfaces here. Note: in the current code this throw is caught at the call site (apps/meteor/server/api/v1/omnichannel/sms.ts:223), logged as 'Attachment upload failed', and the message is still delivered with a placeholder 'Attachment upload failed' attachment — so you normally see it in logs, not as an HTTP error to the SMS provider.","triggerScenarios":"An inbound SMS with a media URL that returns 403/404 (Twilio media links expire after a while), a media host not present in the SSRF_Allowlist setting so serverFetch blocks it, a provider returning 200 with a zero-byte body, or a media URL pointing at an internal/private host that the SSRF guard forbids.","commonSituations":"Replaying or retrying old Twilio webhooks whose media URLs have expired; a self-hosted SMS gateway (e.g. a custom service integration) serving media from an internal domain; workspaces that locked down SSRF_Allowlist after a security review and forgot the SMS provider's media CDN.","solutions":["Add the SMS provider's media host (e.g. *.twilionondialog.com / your gateway domain) to Administration -> Settings -> General -> SSRF Allowlist (SSRF_Allowlist)","Curl the exact media URL from the Rocket.Chat server host to confirm it returns 200 with bytes","Process webhooks promptly — provider media URLs are time-limited; avoid long queue/retry delays","Check the server log for the 'SMS' logger entry 'Attachment upload failed' to see the underlying fetch error"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"// If you control the SMS gateway, pre-validate the media URL the way the server will fetch it:\nconst check = await fetch(mediaUrl, { method: 'HEAD' });\nif (check.status !== 200) log.warn('media not fetchable yet', mediaUrl);","typeGuard":null,"tryCatchPattern":"// The webhook itself is server-side; guard as the SMS integrator by re-fetching media promptly:\nfor (let attempt = 1; attempt <= 3; attempt++) {\n  try { return await downloadMedia(url); } // 200 + non-empty body required\n  catch (e) { await backoff(attempt); } // provider CDNs recover / URLs unblock after allowlist change\n}\nthrow new Error('media download failed after retries');","preventionTips":["Pre-register every media host your SMS providers use in SSRF_Allowlist","Fetch attachments immediately on webhook receipt — media URLs expire","Monitor the 'Attachment upload failed' log pattern to catch silent attachment loss"],"tags":["sms","omnichannel","file-upload","ssrf","webhook","attachments"],"backgroundTag":"file-download-failed","analyzedSha":"b2c16d5842cbe6b69b59bdf6fc5e5f1afcd1f0b0","analyzedAt":"2026-08-18T15:26:39.429Z","contentChangedAt":"2026-08-18T15:26:39.429Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}