RocketChat/Rocket.Chat · error · MeteorError

error-encrypted-private-rooms-enforced

error-encrypted-private-rooms-enforced

Error message

Workspace policy requires all private rooms to be encrypted.

What it means

Thrown by Rocket.Chat's 'beforeCreateRoom' E2E hook when a workspace has both E2E_Enable and E2E_Force_Encryption_For_Private_Rooms enabled and a caller creates a non-federated private channel (type 'p') with an explicit encrypted: false in extraData. Under this policy every new private room must be end-to-end encrypted, so an explicit opt-out is rejected rather than silently overridden. Omitting the encrypted flag never throws — the hook coerces it to true (beforeCreateRoom.ts sets extraData.encrypted = true).

Source

Thrown at apps/meteor/server/lib/e2e/beforeCreateRoom.ts:19

import { MeteorError } from '@rocket.chat/core-services';

import { settings } from '../../settings';
import { prepareCreateRoomCallback } from '../callbacks/beforeCreateRoomCallback';

prepareCreateRoomCallback.add(({ type, extraData }) => {
	if (!settings.get<boolean>('E2E_Enable')) {
		return;
	}

	if (
		(type === 'd' && settings.get<boolean>('E2E_Enabled_Default_DirectRooms')) ||
		(type === 'p' && settings.get<boolean>('E2E_Enabled_Default_PrivateRooms'))
	) {
		extraData.encrypted = extraData.encrypted ?? true;
	}
	if (type === 'p' && extraData.federated !== true && settings.get<boolean>('E2E_Force_Encryption_For_Private_Rooms')) {
		if (extraData.encrypted === false) {
			throw new MeteorError('error-encrypted-private-rooms-enforced', 'Workspace policy requires all private rooms to be encrypted.');
		}
		extraData.encrypted = true;
	}
});

View on GitHub (pinned to 2a7de45707)

Solutions

  1. Remove encrypted:false from the createRoom / rooms.create payload and let the server default the room to encrypted — the policy hook sets encrypted=true itself.
  2. If the room genuinely must be unencrypted, create it as a public channel (type 'c') instead of 'p' — the policy only covers private rooms.
  3. Have a workspace admin disable E2E_Force_Encryption_For_Private_Rooms (Administration → Settings → E2E) if the policy was not intended.
  4. If this is a genuinely federated private room, pass federated: true — federated rooms are the only exemption to the force-encryption check.

Example fix

// before — throws error-encrypted-private-rooms-enforced on workspaces
// with E2E_Force_Encryption_For_Private_Rooms enabled
POST /api/v1/rooms.create
{ "name": "secret-project", "type": "p", "encrypted": false }

// after — omit the flag; the beforeCreateRoom hook sets encrypted=true per policy
POST /api/v1/rooms.create
{ "name": "secret-project", "type": "p" }
Defensive patterns

Strategy: validation

Validate before calling

// server-side, before createRoom(...)
import { settings } from '../../settings';

const encryptionEnforced = (): boolean =>
  settings.get<boolean>('E2E_Enable') === true &&
  settings.get<boolean>('E2E_Force_Encryption_For_Private_Rooms') === true;

if (type === 'p' && extraData.federated !== true && encryptionEnforced()) {
  delete extraData.encrypted; // never send an explicit opt-out; the hook sets encrypted=true
}

Type guard

const isExplicitEncryptionOptOut = (
  extraData: { encrypted?: boolean },
): extraData is { encrypted: false } => extraData.encrypted === false;

Try / catch

try {
  await Meteor.callAsync('createPrivateGroup', { name, members, encrypted: false });
} catch (error) {
  if (error instanceof Meteor.Error && error.error === 'error-encrypted-private-rooms-enforced') {
    // policy workspace: retry without the explicit opt-out — the server defaults to encrypted
    return Meteor.callAsync('createPrivateGroup', { name, members });
  }
  throw error;
}

Prevention

When it happens

Trigger: Calling createRoom('p', name, user, members, readOnly, { encrypted: false }) server-side, or any surface built on it (Meteor method createPrivateGroup, REST POST /api/v1/rooms.create with type 'p' and encrypted:false), while E2E_Enable=true, E2E_Force_Encryption_For_Private_Rooms=true, and extraData.federated !== true. Only an explicit false triggers the throw; encrypted: undefined or true is accepted and coerced to true.

Common situations: An admin enables 'Force encryption for private rooms' after integration code (apps-engine apps, bots, provisioning scripts, test fixtures) was already hardcoding encrypted:false so the flag is always present in payloads. Also hit when copying request payloads between workspaces/environments where the E2E policy differs, or in fixtures that pin every optional field explicitly.

Related errors


AI-assisted analysis of RocketChat/Rocket.Chat@2a7de45707 (2026-08-21). Data as JSON: /api/errors/23cc31f55b7166fb. Report an issue: GitHub.