RocketChat/Rocket.Chat · warning · Meteor.Error

error-invalid-file-uploaded

error-invalid-file-uploaded

Error message

Invalid file uploaded

What it means

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.

Solutions

  1. Add the SMS provider's media host (e.g. *.twilionondialog.com / your gateway domain) to Administration -> Settings -> General -> SSRF Allowlist (SSRF_Allowlist)
  2. Curl the exact media URL from the Rocket.Chat server host to confirm it returns 200 with bytes
  3. Process webhooks promptly — provider media URLs are time-limited; avoid long queue/retry delays
  4. Check the server log for the 'SMS' logger entry 'Attachment upload failed' to see the underlying fetch error
Defensive patterns

Strategy: retry

Validate before calling

// If you control the SMS gateway, pre-validate the media URL the way the server will fetch it:
const check = await fetch(mediaUrl, { method: 'HEAD' });
if (check.status !== 200) log.warn('media not fetchable yet', mediaUrl);

Try / catch

// The webhook itself is server-side; guard as the SMS integrator by re-fetching media promptly:
for (let attempt = 1; attempt <= 3; attempt++) {
  try { return await downloadMedia(url); } // 200 + non-empty body required
  catch (e) { await backoff(attempt); } // provider CDNs recover / URLs unblock after allowlist change
}
throw new Error('media download failed after retries');

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@b2c16d5842 (2026-08-18). Data as JSON: /api/errors/309a2e8fd9de79c3. Report an issue: GitHub.

Appendix: source

Thrown at apps/meteor/server/api/v1/omnichannel/sms.ts:40

import type { ILivechatMessage } from '../../../lib/omnichannel/localTypes';
import { sendMessage } from '../../../lib/omnichannel/messages';
import { createRoom } from '../../../lib/omnichannel/rooms';
import { settings } from '../../../settings';

const logger = new Logger('SMS');

const getUploadFile = async (details: Omit<IUpload, '_id' | '_updatedAt'>, fileUrl: string) => {
	const response = await fetch(fileUrl, {
		ignoreSsrfValidation: false,
		allowList: settings.get<string>('SSRF_Allowlist'),
	});

	const content = Buffer.from(await response.arrayBuffer());

	const contentSize = content.length;

	if (response.status !== 200 || contentSize === 0) {
		throw new Meteor.Error('error-invalid-file-uploaded', 'Invalid file uploaded');
	}

	const fileStore = FileUpload.getStore('Uploads');

	return fileStore.insert({ ...details, size: contentSize }, content);
};

const defineDepartment = async (idOrName?: string) => {
	if (!idOrName || idOrName === '') {
		return;
	}

	const department = await LivechatDepartment.findOneByIdOrName(idOrName, { projection: { _id: 1 } });
	return department?._id;
};

const defineVisitor = async (smsNumber: string, targetDepartment?: string) => {
	const visitor = await LivechatVisitors.findOneVisitorByPhone(smsNumber);

View on GitHub (pinned to b2c16d5842)