{"record":{"id":"23cc31f55b7166fb","repo":"RocketChat/Rocket.Chat","slug":"error-encrypted-private-rooms-enforced","errorCode":"error-encrypted-private-rooms-enforced","errorMessage":"Workspace policy requires all private rooms to be encrypted.","messagePattern":"Workspace policy requires all private rooms to be encrypted\\.","errorType":"exception","errorClass":"MeteorError","httpStatus":null,"severity":"error","filePath":"apps/meteor/server/lib/e2e/beforeCreateRoom.ts","lineNumber":19,"sourceCode":"import { MeteorError } from '@rocket.chat/core-services';\n\nimport { settings } from '../../settings';\nimport { prepareCreateRoomCallback } from '../callbacks/beforeCreateRoomCallback';\n\nprepareCreateRoomCallback.add(({ type, extraData }) => {\n\tif (!settings.get<boolean>('E2E_Enable')) {\n\t\treturn;\n\t}\n\n\tif (\n\t\t(type === 'd' && settings.get<boolean>('E2E_Enabled_Default_DirectRooms')) ||\n\t\t(type === 'p' && settings.get<boolean>('E2E_Enabled_Default_PrivateRooms'))\n\t) {\n\t\textraData.encrypted = extraData.encrypted ?? true;\n\t}\n\tif (type === 'p' && extraData.federated !== true && settings.get<boolean>('E2E_Force_Encryption_For_Private_Rooms')) {\n\t\tif (extraData.encrypted === false) {\n\t\t\tthrow new MeteorError('error-encrypted-private-rooms-enforced', 'Workspace policy requires all private rooms to be encrypted.');\n\t\t}\n\t\textraData.encrypted = true;\n\t}\n});\n","sourceCodeStart":1,"sourceCodeEnd":24,"githubUrl":"https://github.com/RocketChat/Rocket.Chat/blob/2a7de457074cbb4d4373fbd9a4e5bea292c9c764/apps/meteor/server/lib/e2e/beforeCreateRoom.ts#L1-L24","documentation":"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).","triggerScenarios":"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.","commonSituations":"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.","solutions":["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.","If the room genuinely must be unencrypted, create it as a public channel (type 'c') instead of 'p' — the policy only covers private rooms.","Have a workspace admin disable E2E_Force_Encryption_For_Private_Rooms (Administration → Settings → E2E) if the policy was not intended.","If this is a genuinely federated private room, pass federated: true — federated rooms are the only exemption to the force-encryption check."],"exampleFix":"// before — throws error-encrypted-private-rooms-enforced on workspaces\n// with E2E_Force_Encryption_For_Private_Rooms enabled\nPOST /api/v1/rooms.create\n{ \"name\": \"secret-project\", \"type\": \"p\", \"encrypted\": false }\n\n// after — omit the flag; the beforeCreateRoom hook sets encrypted=true per policy\nPOST /api/v1/rooms.create\n{ \"name\": \"secret-project\", \"type\": \"p\" }","handlingStrategy":"validation","validationCode":"// server-side, before createRoom(...)\nimport { settings } from '../../settings';\n\nconst encryptionEnforced = (): boolean =>\n  settings.get<boolean>('E2E_Enable') === true &&\n  settings.get<boolean>('E2E_Force_Encryption_For_Private_Rooms') === true;\n\nif (type === 'p' && extraData.federated !== true && encryptionEnforced()) {\n  delete extraData.encrypted; // never send an explicit opt-out; the hook sets encrypted=true\n}","typeGuard":"const isExplicitEncryptionOptOut = (\n  extraData: { encrypted?: boolean },\n): extraData is { encrypted: false } => extraData.encrypted === false;","tryCatchPattern":"try {\n  await Meteor.callAsync('createPrivateGroup', { name, members, encrypted: false });\n} catch (error) {\n  if (error instanceof Meteor.Error && error.error === 'error-encrypted-private-rooms-enforced') {\n    // policy workspace: retry without the explicit opt-out — the server defaults to encrypted\n    return Meteor.callAsync('createPrivateGroup', { name, members });\n  }\n  throw error;\n}","preventionTips":["Treat optional booleans as 'unset by default' — omit encrypted unless the caller made an explicit user-facing choice.","Feature-detect the workspace policy (E2E_Enable + E2E_Force_Encryption_For_Private_Rooms) before offering an 'unencrypted private channel' option in UI or integrations.","Do not share hardcoded request payloads across environments; a payload recorded on a non-E2E workspace violates policy on an E2E-enforcing one.","Remember federated private rooms are the only exemption to the force-encryption check — and federation is a real setup, not a bypass flag."],"tags":["e2e","encryption","private-channels","workspace-policy","room-creation","rocket-chat"],"backgroundTag":"e2e-encryption-policy","analyzedSha":"2a7de457074cbb4d4373fbd9a4e5bea292c9c764","analyzedAt":"2026-08-21T15:01:34.830Z","contentChangedAt":"2026-08-21T15:01:34.830Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}