RocketChat/Rocket.Chat · error · Meteor.Error

error-invalid-file-uploaded

error-invalid-file-uploaded

Error message

Invalid file uploaded

What it means

Thrown by getUploadFile in the SMS-incoming handler when the fetched file URL returns a non-200 status or an empty body. Rocket.Chat fetches SMS attachments (MMS) from the provider's URL, validates SSRF against the allow-list, then rejects empty or failed downloads with this Meteor.Error.

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 f9d3ec372b)

Solutions

  1. Verify the fileUrl is reachable from the Rocket.Chat server (curl -I from the host).
  2. Add the provider/CDN domain to the SSRF_Allowlist setting if the fetch is being denied.
  3. Coordinate with the SMS provider to keep attachment URLs alive long enough to fetch.
  4. Log response.status and contentSize to identify provider vs network issues; retry transient 5xx.

Example fix

// before
const content = await fetchAttachment(providerUrl); // black-box

// after
const resp = await fetch(providerUrl, { allowList: settings.get('SSRF_Allowlist') });
if (resp.status !== 200 || (await resp.arrayBuffer()).byteLength === 0) {
  logger.warn('attachment fetch failed', { status: resp.status, url: providerUrl });
  return; // drop the attachment, keep the text
}
await storeAttachment(resp);
Defensive patterns

Strategy: try-catch

Validate before calling

async function preflightAttachment(url: string, allowList: string) {
  const resp = await fetch(url, { allowList });
  if (resp.status !== 200) return { ok: false, status: resp.status };
  const buf = await resp.arrayBuffer();
  return { ok: buf.byteLength > 0, size: buf.byteLength };
}

Type guard

null

Try / catch

try {
  await processSmsIncoming(payload);
} catch (e) {
  if (e.error === 'error-invalid-file-uploaded') { logger.warn('attachment dropped', e); return API.v1.success(); /* still accept text */ }
  throw e;
}

Prevention

When it happens

Trigger: POST livechat/sms-incoming/:service where the parsed attachment fileUrl returns 404/403/500 or a 200 with zero bytes. Also when the URL host is not in SSRF_Allowlist the fetch may resolve to a non-200 (SSRF rejection surfaces differently but can still produce non-200).

Common situations: SMS/MMS provider link expired or revoked; provider serves a redirect to a CDN that 404s; provider returns 200 with empty body on unsupported media; the fileUrl is behind auth the Rocket.Chat server cannot satisfy; SSRF_Allowlist misconfigured so the fetch is denied and returns non-200.

Related errors


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