From 53555ad72aae884f1472b89d3a426f719df2fede Mon Sep 17 00:00:00 2001 From: Srinjoyee_Dey Date: Thu, 5 Feb 2026 23:02:57 +0530 Subject: [PATCH 1/3] fix(security): escape external inputs in LivechatRooms regex queries --- packages/models/src/models/LivechatRooms.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/packages/models/src/models/LivechatRooms.ts b/packages/models/src/models/LivechatRooms.ts index d388e7a39fd6e..de82449523ef6 100644 --- a/packages/models/src/models/LivechatRooms.ts +++ b/packages/models/src/models/LivechatRooms.ts @@ -1330,8 +1330,9 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive query.tags = { $in: tags }; } if (customFields && Object.keys(customFields).length) { + // Escape custom field values to prevent query failures and ReDoS query.$and = Object.keys(customFields).map((key) => ({ - [`livechatData.${key}`]: new RegExp(customFields[key], 'i'), + [`livechatData.${key}`]: new RegExp(escapeRegExp(customFields[key]), 'i'), })); } @@ -1827,7 +1828,11 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive const query: Filter = { 't': 'l', 'v.token': visitorToken, - '$or': [{ 'email.thread': { $elemMatch: { $in: emailThread } } }, { 'email.thread': new RegExp(emailThread.join('|')) }], + '$or': [ + { 'email.thread': { $elemMatch: { $in: emailThread } } }, + // Escape email thread IDs to prevent query failures and ReDoS + { 'email.thread': new RegExp(emailThread.map((t) => escapeRegExp(t)).join('|')) }, + ], }; return this.findOne(query, options); @@ -1844,7 +1849,8 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive 'v.token': visitorToken, '$or': [ { 'email.thread': { $elemMatch: { $in: emailThread } } }, - { 'email.thread': new RegExp(emailThread.map((t) => `"${t}"`).join('|')) }, + // Escape email thread IDs to prevent query failures and ReDoS + { 'email.thread': new RegExp(emailThread.map((t) => `"${escapeRegExp(t)}"`).join('|')) }, ], ...(departmentId && { departmentId }), }; @@ -1857,7 +1863,11 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive 't': 'l', 'open': true, 'v.token': visitorToken, - '$or': [{ 'email.thread': { $elemMatch: { $in: emailThread } } }, { 'email.thread': new RegExp(emailThread.join('|')) }], + '$or': [ + { 'email.thread': { $elemMatch: { $in: emailThread } } }, + // Escape email thread IDs to prevent query failures and ReDoS + { 'email.thread': new RegExp(emailThread.map((t) => escapeRegExp(t)).join('|')) }, + ], }; return this.findOne(query, options); From dd9088efd7fcf3a363b208c7a6f3405863ef86de Mon Sep 17 00:00:00 2001 From: Srinjoyee_Dey Date: Thu, 5 Feb 2026 23:29:22 +0530 Subject: [PATCH 2/3] security: fix regex vulnerabilities and standardize usage in models --- packages/models/src/models/LivechatRooms.ts | 2 +- .../models/src/models/ModerationReports.ts | 24 ++++++++++--------- packages/models/src/models/Sessions.ts | 6 +++-- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/packages/models/src/models/LivechatRooms.ts b/packages/models/src/models/LivechatRooms.ts index de82449523ef6..c8927c9f0318f 100644 --- a/packages/models/src/models/LivechatRooms.ts +++ b/packages/models/src/models/LivechatRooms.ts @@ -1850,7 +1850,7 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive '$or': [ { 'email.thread': { $elemMatch: { $in: emailThread } } }, // Escape email thread IDs to prevent query failures and ReDoS - { 'email.thread': new RegExp(emailThread.map((t) => `"${escapeRegExp(t)}"`).join('|')) }, + { 'email.thread': new RegExp(emailThread.map((t) => escapeRegExp(t)).join('|')) }, ], ...(departmentId && { departmentId }), }; diff --git a/packages/models/src/models/ModerationReports.ts b/packages/models/src/models/ModerationReports.ts index 196b5161aef5c..acb715c55491c 100644 --- a/packages/models/src/models/ModerationReports.ts +++ b/packages/models/src/models/ModerationReports.ts @@ -9,6 +9,8 @@ import type { import type { FindPaginated, IModerationReportsModel, PaginationParams } from '@rocket.chat/model-typings'; import type { AggregationCursor, Collection, Db, Document, FindCursor, FindOptions, IndexDescription, UpdateResult } from 'mongodb'; +import { escapeRegExp } from '@rocket.chat/string-helpers'; + import { BaseRaw } from './BaseRaw'; import { readSecondaryPreferred } from '../readSecondaryPreferred'; @@ -239,11 +241,11 @@ export class ModerationReportsRaw extends BaseRaw implements const fuzzyQuery = selector ? { - 'message.msg': { - $regex: selector, - $options: 'i', - }, - } + 'message.msg': { + $regex: escapeRegExp(selector), + $options: 'i', + }, + } : {}; const params = { @@ -388,25 +390,25 @@ export class ModerationReportsRaw extends BaseRaw implements $or: [ { 'message.msg': { - $regex: selector, + $regex: escapeRegExp(selector), $options: 'i', }, }, { description: { - $regex: selector, + $regex: escapeRegExp(selector), $options: 'i', }, }, { 'message.u.username': { - $regex: selector, + $regex: escapeRegExp(selector), $options: 'i', }, }, { 'message.u.name': { - $regex: selector, + $regex: escapeRegExp(selector), $options: 'i', }, }, @@ -424,13 +426,13 @@ export class ModerationReportsRaw extends BaseRaw implements $or: [ { 'reportedUser.username': { - $regex: selector, + $regex: escapeRegExp(selector), $options: 'i', }, }, { 'reportedUser.name': { - $regex: selector, + $regex: escapeRegExp(selector), $options: 'i', }, }, diff --git a/packages/models/src/models/Sessions.ts b/packages/models/src/models/Sessions.ts index 3763bf38b457a..eb481d806418f 100644 --- a/packages/models/src/models/Sessions.ts +++ b/packages/models/src/models/Sessions.ts @@ -28,6 +28,8 @@ import type { FindOptions, } from 'mongodb'; +import { escapeRegExp } from '@rocket.chat/string-helpers'; + import { getCollectionName } from '../index'; import { BaseRaw } from './BaseRaw'; import { readSecondaryPreferred } from '../readSecondaryPreferred'; @@ -755,7 +757,7 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { offset?: number; count?: number; }): Promise> { - const searchQuery = search ? [{ searchTerm: { $regex: search, $options: 'i' } }] : []; + const searchQuery = search ? [{ searchTerm: { $regex: escapeRegExp(search), $options: 'i' } }] : []; const matchOperator = { $match: { @@ -861,7 +863,7 @@ export class SessionsRaw extends BaseRaw implements ISessionsModel { offset?: number; count?: number; }): Promise> { - const searchQuery = search ? [{ searchTerm: { $regex: search, $options: 'i' } }] : []; + const searchQuery = search ? [{ searchTerm: { $regex: escapeRegExp(search), $options: 'i' } }] : []; const matchOperator = { $match: { From 694277d7a3709a4bff534b9357f8655137cd1179 Mon Sep 17 00:00:00 2001 From: Srinjoyee_Dey Date: Thu, 5 Feb 2026 23:37:54 +0530 Subject: [PATCH 3/3] security: strip inline comments from implementation --- packages/models/src/models/LivechatRooms.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/models/src/models/LivechatRooms.ts b/packages/models/src/models/LivechatRooms.ts index c8927c9f0318f..eabcf3a50aa86 100644 --- a/packages/models/src/models/LivechatRooms.ts +++ b/packages/models/src/models/LivechatRooms.ts @@ -1849,7 +1849,6 @@ export class LivechatRoomsRaw extends BaseRaw implements ILive 'v.token': visitorToken, '$or': [ { 'email.thread': { $elemMatch: { $in: emailThread } } }, - // Escape email thread IDs to prevent query failures and ReDoS { 'email.thread': new RegExp(emailThread.map((t) => escapeRegExp(t)).join('|')) }, ], ...(departmentId && { departmentId }),