diff --git a/app/api/server/v1/invites.js b/app/api/server/v1/invites.js
index 9409458e3093a3a0d329af7d498c5f1b3ac08e63..fd17ec3661908154747467f164621831574fe95e 100644
--- a/app/api/server/v1/invites.js
+++ b/app/api/server/v1/invites.js
@@ -1,5 +1,3 @@
-import { Meteor } from 'meteor/meteor';
-
 import { API } from '../api';
 import { findOrCreateInvite } from '../../../invites/server/functions/findOrCreateInvite';
 import { removeInvite } from '../../../invites/server/functions/removeInvite';
@@ -46,10 +44,6 @@ API.v1.addRoute('validateInviteToken', { authRequired: false }, {
 	post() {
 		const { token } = this.bodyParams;
 
-		if (!token) {
-			throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'validateInviteToken', field: 'token' });
-		}
-
 		let valid = true;
 		try {
 			validateInviteToken(token);
diff --git a/app/invites/server/functions/validateInviteToken.js b/app/invites/server/functions/validateInviteToken.js
index 88d35fd5ccab9f1e011589541681dc73510a1b35..dda8add8b6123428edc05af495e2e3831e3b24fa 100644
--- a/app/invites/server/functions/validateInviteToken.js
+++ b/app/invites/server/functions/validateInviteToken.js
@@ -3,7 +3,7 @@ import { Meteor } from 'meteor/meteor';
 import { Invites, Rooms } from '../../../models';
 
 export const validateInviteToken = (token) => {
-	if (!token) {
+	if (!token || typeof token !== 'string') {
 		throw new Meteor.Error('error-invalid-token', 'The invite token is invalid.', { method: 'validateInviteToken', field: 'token' });
 	}
 
diff --git a/app/lib/server/methods/getFullUserData.js b/app/lib/server/methods/getFullUserData.js
index 07ae554acb4e3e3525fe7db77ad5a22b875e5d5e..3c551dacd1ee53a7011d15c216c0ae48b00fc739 100644
--- a/app/lib/server/methods/getFullUserData.js
+++ b/app/lib/server/methods/getFullUserData.js
@@ -4,7 +4,14 @@ import { getFullUserData } from '../functions';
 
 Meteor.methods({
 	getFullUserData({ filter = '', username = '', limit = 1 }) {
+		console.warn('Method "getFullUserData" is deprecated and will be removed after v4.0.0');
+
+		if (!Meteor.userId()) {
+			throw new Meteor.Error('not-authorized');
+		}
+
 		const result = getFullUserData({ userId: Meteor.userId(), filter: filter || username, limit });
+
 		return result && result.fetch();
 	},
 });
diff --git a/app/lib/server/methods/getServerInfo.js b/app/lib/server/methods/getServerInfo.js
index 2c76421adb5a4d4b97808cf6731424b13a3e1c92..4445eaf36f3580017e6f9520c0c885e977fc1aff 100644
--- a/app/lib/server/methods/getServerInfo.js
+++ b/app/lib/server/methods/getServerInfo.js
@@ -4,6 +4,11 @@ import { Info } from '../../../utils';
 
 Meteor.methods({
 	getServerInfo() {
+		if (!Meteor.userId()) {
+			console.warning('Method "getServerInfo" is deprecated and will be removed after v4.0.0');
+			throw new Meteor.Error('not-authorized');
+		}
+
 		return Info;
 	},
 });
diff --git a/app/livechat/server/methods/loadHistory.js b/app/livechat/server/methods/loadHistory.js
index 395ea3ea5a94d6d2c9da0cd5c192ff065a404c71..0ac3331e217a9a2f5c3cfa36e114b4d847ae57e5 100644
--- a/app/livechat/server/methods/loadHistory.js
+++ b/app/livechat/server/methods/loadHistory.js
@@ -5,6 +5,10 @@ import { LivechatVisitors } from '../../../models';
 
 Meteor.methods({
 	'livechat:loadHistory'({ token, rid, end, limit = 20, ls }) {
+		if (!token || typeof token !== 'string') {
+			return;
+		}
+
 		const visitor = LivechatVisitors.getVisitorByToken(token, { fields: { _id: 1 } });
 
 		if (!visitor) {
diff --git a/app/livechat/server/methods/saveOfficeHours.js b/app/livechat/server/methods/saveOfficeHours.js
index a84b3b7a2fe43785b56339b1390892d744061686..d0e16a59843bc63c50402cc8bf7cf42b348459f9 100644
--- a/app/livechat/server/methods/saveOfficeHours.js
+++ b/app/livechat/server/methods/saveOfficeHours.js
@@ -1,10 +1,16 @@
 import { Meteor } from 'meteor/meteor';
 
+import { hasPermission } from '../../../authorization';
 import { LivechatBusinessHours } from '../../../models/server/raw';
 
 Meteor.methods({
 	'livechat:saveOfficeHours'(day, start, finish, open) {
-		console.log('Method "livechat:saveOfficeHour" is deprecated and will be removed after v4.0.0');
+		console.warn('Method "livechat:saveOfficeHour" is deprecated and will be removed after v4.0.0');
+
+		if (!Meteor.userId() || !hasPermission(Meteor.userId(), 'view-livechat-business-hours')) {
+			throw new Meteor.Error('error-not-allowed', 'Not allowed', { method: 'livechat:saveOfficeHours' });
+		}
+
 		LivechatBusinessHours.updateDayOfGlobalBusinessHour({
 			day,
 			start,
diff --git a/app/markdown/lib/parser/marked/marked.js b/app/markdown/lib/parser/marked/marked.js
index c330bddbaab618fbcec2fc7e8c08c5746cd11221..e6923893b335d77afbfc81324a0970c9c325640b 100644
--- a/app/markdown/lib/parser/marked/marked.js
+++ b/app/markdown/lib/parser/marked/marked.js
@@ -1,7 +1,7 @@
 import { Random } from 'meteor/random';
 import _ from 'underscore';
 import _marked from 'marked';
-
+import dompurify from 'dompurify';
 
 import hljs from '../../hljs';
 import { escapeHTML } from '../../../../../lib/escapeHTML';
@@ -107,5 +107,7 @@ export const marked = (message, {
 		highlight,
 	});
 
+	message.html = dompurify.sanitize(message.html);
+
 	return message;
 };
diff --git a/app/markdown/lib/parser/original/markdown.js b/app/markdown/lib/parser/original/markdown.js
index f7b0279e6c442d7b92dd418b8fa0f959b690bb1b..637037ac57d6d42f914044ca6056d189cb32fa24 100644
--- a/app/markdown/lib/parser/original/markdown.js
+++ b/app/markdown/lib/parser/original/markdown.js
@@ -14,7 +14,17 @@ const addAsToken = (message, html) => {
 	return token;
 };
 
-const validateUrl = (url) => {
+const validateUrl = (url, message) => {
+	// Don't render markdown inside links
+	if (message?.tokens?.some((token) => url.includes(token.token))) {
+		return false;
+	}
+
+	// Valid urls don't contain whitespaces
+	if (/\s/.test(url.trim())) {
+		return false;
+	}
+
 	try {
 		new URL(url);
 		return true;
@@ -76,36 +86,37 @@ const parseNotEscaped = (message, {
 
 	// Support ![alt text](http://image url)
 	msg = msg.replace(new RegExp(`!\\[([^\\]]+)\\]\\(((?:${ schemes }):\\/\\/[^\\s]+)\\)`, 'gm'), (match, title, url) => {
-		if (!validateUrl(url)) {
+		if (!validateUrl(url, message)) {
 			return match;
 		}
+		url = encodeURI(url);
+
 		const target = url.indexOf(rootUrl) === 0 ? '' : '_blank';
 		return addAsToken(message, `<a href="${ url }" title="${ title }" target="${ target }" rel="noopener noreferrer"><div class="inline-image" style="background-image: url(${ url });"></div></a>`);
 	});
 
 	// Support [Text](http://link)
 	msg = msg.replace(new RegExp(`\\[([^\\]]+)\\]\\(((?:${ schemes }):\\/\\/[^\\s]+)\\)`, 'gm'), (match, title, url) => {
-		if (!validateUrl(url)) {
+		if (!validateUrl(url, message)) {
 			return match;
 		}
 		const target = url.indexOf(rootUrl) === 0 ? '' : '_blank';
 		title = title.replace(/&amp;/g, '&');
 
-		let escapedUrl = url;
-		escapedUrl = escapedUrl.replace(/&amp;/g, '&');
+		const escapedUrl = encodeURI(url);
 
 		return addAsToken(message, `<a href="${ escapedUrl }" target="${ target }" rel="noopener noreferrer">${ title }</a>`);
 	});
 
 	// Support <http://link|Text>
-	msg = msg.replace(new RegExp(`(?:<|&lt;)((?:${ schemes }):\\/\\/[^\\|]+)\\|(.+?)(?=>|&gt;)(?:>|&gt;)`, 'gm'), (match, url, title) => {
-		if (!validateUrl(url)) {
+	msg = msg.replace(new RegExp(`(?:<|&lt;)((?:${ schemes }):\\\/\\\/[^\\|]+)\\|(.+?)(?=>|&gt;)(?:>|&gt;)`, 'gm'), (match, url, title) => {
+		if (!validateUrl(url, message)) {
 			return match;
 		}
+		url = encodeURI(url);
 		const target = url.indexOf(rootUrl) === 0 ? '' : '_blank';
 		return addAsToken(message, `<a href="${ url }" target="${ target }" rel="noopener noreferrer">${ title }</a>`);
 	});
-
 	return msg;
 };
 
diff --git a/app/markdown/tests/client.tests.js b/app/markdown/tests/client.tests.js
index 3d6e175f1de044535624d7d9a9971c71110437b2..fd92206771d84ef965f2cbc640ec14357cb9769d 100644
--- a/app/markdown/tests/client.tests.js
+++ b/app/markdown/tests/client.tests.js
@@ -157,7 +157,7 @@ const link = {
 	'&lt;http://link|Text&gt;': escapeHTML('&lt;http://link|Text&gt;'),
 	'&lt;https://open.rocket.chat/|Open Site For Rocket.Chat&gt;': escapeHTML('&lt;https://open.rocket.chat/|Open Site For Rocket.Chat&gt;'),
 	'&lt;https://open.rocket.chat/ | Open Site For Rocket.Chat&gt;': escapeHTML('&lt;https://open.rocket.chat/ | Open Site For Rocket.Chat&gt;'),
-	'&lt;https://rocket.chat/|Rocket.Chat Site&gt;': escapeHTML('&lt;https://rocket.chat/|Rocket.Chat Site&gt;'),
+	'&lt;https://rocket.chat/|Rocket.Chat Site&gt;': '&amp;lt;https://rocket.chat/|Rocket.Chat Site&amp;gt;',
 	'&lt;https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;': escapeHTML('&lt;https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site&gt;'),
 	'&lt;http://linkText&gt;': escapeHTML('&lt;http://linkText&gt;'),
 	'&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;': escapeHTML('&lt;https:open.rocket.chat/ | Open Site For Rocket.Chat&gt;'),
@@ -172,7 +172,7 @@ const link = {
 	'<http://invalid link|Text>': escapeHTML('<http://invalid link|Text>'),
 	'<http://link|Text>': linkWrapped('http://link', 'Text'),
 	'<https://open.rocket.chat/|Open Site For Rocket.Chat>': linkWrapped('https://open.rocket.chat/', 'Open Site For Rocket.Chat'),
-	'<https://open.rocket.chat/ | Open Site For Rocket.Chat>': linkWrapped('https://open.rocket.chat/ ', ' Open Site For Rocket.Chat'),
+	'<https://open.rocket.chat/ | Open Site For Rocket.Chat>': linkWrapped(encodeURI('https://open.rocket.chat/ '), ' Open Site For Rocket.Chat'),
 	'<https://rocket.chat/|Rocket.Chat Site>': linkWrapped('https://rocket.chat/', 'Rocket.Chat Site'),
 	'<https://rocket.chat/docs/developer-guides/testing/#testing|Testing Entry on Rocket.Chat Docs Site>': linkWrapped('https://rocket.chat/docs/developer-guides/testing/#testing', 'Testing Entry on Rocket.Chat Docs Site'),
 	'<http://linkText>': escapeHTML('<http://linkText>'),
@@ -199,7 +199,7 @@ const link = {
 	'[Rocket.Chat Site](tps://rocket.chat/)': '[Rocket.Chat Site](tps://rocket.chat/)',
 	'[Open Site For Rocket.Chat](open.rocket.chat/)': '[Open Site For Rocket.Chat](open.rocket.chat/)',
 	'[Testing Entry on Rocket.Chat Docs Site](htts://rocket.chat/docs/developer-guides/testing/#testing)': '[Testing Entry on Rocket.Chat Docs Site](htts://rocket.chat/docs/developer-guides/testing/#testing)',
-	'[Text](http://link?param1=1&param2=2)': linkWrapped('http://link?param1=1&param2=2', 'Text'),
+	'[Text](http://link?param1=1&param2=2)': linkWrapped('http://link?param1=1&amp;param2=2', 'Text'),
 	'[Testing Double parentheses](https://en.wikipedia.org/wiki/Disambiguation_(disambiguation))': linkWrapped('https://en.wikipedia.org/wiki/Disambiguation_(disambiguation)', 'Testing Double parentheses'),
 	'[Testing data after Double parentheses](https://en.wikipedia.org/wiki/Disambiguation_(disambiguation)/blabla/bla)': linkWrapped('https://en.wikipedia.org/wiki/Disambiguation_(disambiguation)/blabla/bla', 'Testing data after Double parentheses'),
 };
diff --git a/app/message-mark-as-unread/server/unreadMessages.js b/app/message-mark-as-unread/server/unreadMessages.js
index 246a33e087d5bdaab27be257c973bc7fc9040dfe..aec92fcf94b6dc165ee1b50a74939f64eb240a0e 100644
--- a/app/message-mark-as-unread/server/unreadMessages.js
+++ b/app/message-mark-as-unread/server/unreadMessages.js
@@ -12,8 +12,8 @@ Meteor.methods({
 			});
 		}
 
-		if (room) {
-			const lastMessage = Messages.findVisibleByRoomId({ rid: room, queryOptions: { limit: 1, sort: { ts: -1 } } }).fetch()[0];
+		if (room && typeof room === 'string') {
+			const lastMessage = Messages.findVisibleByRoomId(room, { limit: 1, sort: { ts: -1 } }).fetch()[0];
 
 			if (lastMessage == null) {
 				throw new Meteor.Error('error-action-not-allowed', 'Not allowed', {
@@ -25,6 +25,13 @@ Meteor.methods({
 			return Subscriptions.setAsUnreadByRoomIdAndUserId(lastMessage.rid, userId, lastMessage.ts);
 		}
 
+		if (typeof firstUnreadMessage?._id !== 'string') {
+			throw new Meteor.Error('error-action-not-allowed', 'Not allowed', {
+				method: 'unreadMessages',
+				action: 'Unread_messages',
+			});
+		}
+
 		const originalMessage = Messages.findOneById(firstUnreadMessage._id, {
 			fields: {
 				u: 1,
diff --git a/app/message-pin/client/pinMessage.js b/app/message-pin/client/pinMessage.js
index 9fbc2f778edcf0643f76401748509039892887b8..5843be4de181b752e8efd789b76e662ac7f57b25 100644
--- a/app/message-pin/client/pinMessage.js
+++ b/app/message-pin/client/pinMessage.js
@@ -19,9 +19,14 @@ Meteor.methods({
 			toastr.error(TAPi18n.__('error-pinning-message'));
 			return false;
 		}
+		if (typeof message._id !== 'string') {
+			toastr.error(TAPi18n.__('error-pinning-message'));
+			return false;
+		}
 		toastr.success(TAPi18n.__('Message_has_been_pinned'));
 		return ChatMessage.update({
 			_id: message._id,
+			rid: message.rid,
 		}, {
 			$set: {
 				pinned: true,
@@ -41,9 +46,14 @@ Meteor.methods({
 			toastr.error(TAPi18n.__('error-unpinning-message'));
 			return false;
 		}
+		if (typeof message._id !== 'string') {
+			toastr.error(TAPi18n.__('error-unpinning-message'));
+			return false;
+		}
 		toastr.success(TAPi18n.__('Message_has_been_unpinned'));
 		return ChatMessage.update({
 			_id: message._id,
+			rid: message.rid,
 		}, {
 			$set: {
 				pinned: false,
diff --git a/app/message-pin/server/pinMessage.js b/app/message-pin/server/pinMessage.js
index a62ef8b7138f6e2322d0c9562a9731d265a774aa..4543496ad2a073575cf0c51c20facf61e723313f 100644
--- a/app/message-pin/server/pinMessage.js
+++ b/app/message-pin/server/pinMessage.js
@@ -1,4 +1,5 @@
 import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
 
 import { settings } from '../../settings';
 import { callbacks } from '../../callbacks';
@@ -28,6 +29,8 @@ const shouldAdd = (attachments, attachment) => !attachments.some(({ message_link
 
 Meteor.methods({
 	pinMessage(message, pinnedAt) {
+		check(message._id, String);
+
 		const userId = Meteor.userId();
 		if (!userId) {
 			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
@@ -42,30 +45,34 @@ Meteor.methods({
 			});
 		}
 
-		if (!hasPermission(Meteor.userId(), 'pin-message', message.rid)) {
-			throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
+		let originalMessage = Messages.findOneById(message._id);
+		if (originalMessage == null || originalMessage._id == null) {
+			throw new Meteor.Error('error-invalid-message', 'Message you are pinning was not found', {
+				method: 'pinMessage',
+				action: 'Message_pinning',
+			});
 		}
 
-		const subscription = Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId(), { fields: { _id: 1 } });
+		const subscription = Subscriptions.findOneByRoomIdAndUserId(originalMessage.rid, Meteor.userId(), { fields: { _id: 1 } });
 		if (!subscription) {
-			return false;
-		}
-
-		let originalMessage = Messages.findOneById(message._id);
-		if (originalMessage == null || originalMessage._id == null) {
+			// If it's a valid message but on a room that the user is not subscribed to, report that the message was not found.
 			throw new Meteor.Error('error-invalid-message', 'Message you are pinning was not found', {
 				method: 'pinMessage',
 				action: 'Message_pinning',
 			});
 		}
 
+		if (!hasPermission(Meteor.userId(), 'pin-message', originalMessage.rid)) {
+			throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
+		}
+
 		const me = Users.findOneById(userId);
 
 		// If we keep history of edits, insert a new message to store history information
 		if (settings.get('Message_KeepHistory')) {
 			Messages.cloneAndSaveAsHistoryById(message._id, me);
 		}
-		const room = Meteor.call('canAccessRoom', message.rid, Meteor.userId());
+		const room = Meteor.call('canAccessRoom', originalMessage.rid, Meteor.userId());
 
 		originalMessage.pinned = true;
 		originalMessage.pinnedAt = pinnedAt || Date.now;
@@ -110,6 +117,8 @@ Meteor.methods({
 		);
 	},
 	unpinMessage(message) {
+		check(message._id, String);
+
 		if (!Meteor.userId()) {
 			throw new Meteor.Error('error-invalid-user', 'Invalid user', {
 				method: 'unpinMessage',
@@ -123,24 +132,27 @@ Meteor.methods({
 			});
 		}
 
-		if (!hasPermission(Meteor.userId(), 'pin-message', message.rid)) {
-			throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'pinMessage' });
+		let originalMessage = Messages.findOneById(message._id);
+		if (originalMessage == null || originalMessage._id == null) {
+			throw new Meteor.Error('error-invalid-message', 'Message you are unpinning was not found', {
+				method: 'unpinMessage',
+				action: 'Message_pinning',
+			});
 		}
 
-		const subscription = Subscriptions.findOneByRoomIdAndUserId(message.rid, Meteor.userId(), { fields: { _id: 1 } });
+		const subscription = Subscriptions.findOneByRoomIdAndUserId(originalMessage.rid, Meteor.userId(), { fields: { _id: 1 } });
 		if (!subscription) {
-			return false;
-		}
-
-		let originalMessage = Messages.findOneById(message._id);
-
-		if (originalMessage == null || originalMessage._id == null) {
+			// If it's a valid message but on a room that the user is not subscribed to, report that the message was not found.
 			throw new Meteor.Error('error-invalid-message', 'Message you are unpinning was not found', {
 				method: 'unpinMessage',
 				action: 'Message_pinning',
 			});
 		}
 
+		if (!hasPermission(Meteor.userId(), 'pin-message', originalMessage.rid)) {
+			throw new Meteor.Error('not-authorized', 'Not Authorized', { method: 'unpinMessage' });
+		}
+
 		const me = Users.findOneById(Meteor.userId());
 
 		// If we keep history of edits, insert a new message to store history information
@@ -154,7 +166,7 @@ Meteor.methods({
 			username: me.username,
 		};
 		originalMessage = callbacks.run('beforeSaveMessage', originalMessage);
-		const room = Meteor.call('canAccessRoom', message.rid, Meteor.userId());
+		const room = Meteor.call('canAccessRoom', originalMessage.rid, Meteor.userId());
 		if (isTheLastMessage(room, message)) {
 			Rooms.setLastMessagePinned(room._id, originalMessage.pinnedBy, originalMessage.pinned);
 		}
diff --git a/app/theme/client/imports/general/base_old.css b/app/theme/client/imports/general/base_old.css
index 0b2e791a5cc65eb0c8735c26d380def2d2610df6..b34decf221cb8403ca23e42cb4874f459622390b 100644
--- a/app/theme/client/imports/general/base_old.css
+++ b/app/theme/client/imports/general/base_old.css
@@ -1312,14 +1312,14 @@
 .rc-old .container-bars {
 	position: relative;
 	z-index: 2;
-	
-	margin: 5px 10px 0;
 
 	display: none;
 	visibility: hidden;
 	overflow: hidden;
 	flex-direction: column;
 
+	margin: 5px 10px 0;
+
 	transition: transform 0.4s ease, visibility 0.3s ease, opacity 0.3s ease;
 	transform: translateY(-10px);
 
diff --git a/client/components/MarkdownText.js b/client/components/MarkdownText.js
index eba2d322148a8fdbc22b6a70389fa09f8b19a2e3..1adc18fd751bdc1b9a335bf85a7ffb1b289cd458 100644
--- a/client/components/MarkdownText.js
+++ b/client/components/MarkdownText.js
@@ -1,8 +1,7 @@
 import { Box } from '@rocket.chat/fuselage';
 import React, { useMemo } from 'react';
 import marked from 'marked';
-
-import { escapeHTML } from '../../lib/escapeHTML';
+import dompurify from 'dompurify';
 
 marked.InlineLexer.rules.gfm.strong = /^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)|^\*(?=\S)([\s\S]*?\S)\*(?!\*)/;
 marked.InlineLexer.rules.gfm.em = /^__(?=\S)([\s\S]*?\S)__(?!_)|^_(?=\S)([\s\S]*?\S)_(?!_)/;
@@ -13,9 +12,12 @@ const options = {
 };
 
 function MarkdownText({ content, preserveHtml = false, withRichContent = true, ...props }) {
-	const __html = useMemo(() => content && marked(preserveHtml ? content : escapeHTML(content), options), [content, preserveHtml]);
-
-	return __html ? <Box dangerouslySetInnerHTML={{ __html }} {...withRichContent && { withRichContent }} {...props} /> : null;
+	const sanitizer = dompurify.sanitize;
+	const __html = useMemo(() => {
+		const html = content && marked(content, options);
+		return preserveHtml ? html : html && sanitizer(html);
+	}, [content, preserveHtml, sanitizer]);
+	return __html ? <Box dangerouslySetInnerHTML={{ __html }} withRichContent={withRichContent} {...props} /> : null;
 }
 
 export default MarkdownText;
diff --git a/package-lock.json b/package-lock.json
index faa94118ceba943b18f16ca65ea8efd69eb41337..b833e288a0e9f5395003f1709d4a86332bc1a616 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17691,6 +17691,11 @@
 				"domelementtype": "1"
 			}
 		},
+		"dompurify": {
+			"version": "2.2.6",
+			"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.2.6.tgz",
+			"integrity": "sha512-7b7ZArhhH0SP6W2R9cqK6RjaU82FZ2UPM7RO8qN1b1wyvC/NY1FNWcX1Pu00fFOAnzEORtwXe4bPaClg6pUybQ=="
+		},
 		"domutils": {
 			"version": "1.5.1",
 			"resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz",
diff --git a/package.json b/package.json
index a68fa685f672ff169c9e4515acb0142370e6b4a9..2329dd975f8116ac068ea589c0b79c3c206931e9 100644
--- a/package.json
+++ b/package.json
@@ -184,6 +184,7 @@
 		"core-js": "^3.8.1",
 		"cors": "^2.8.5",
 		"csv-parse": "^4.12.0",
+		"dompurify": "^2.2.6",
 		"ejson": "^2.2.0",
 		"emailreplyparser": "^0.0.5",
 		"emojione": "^4.5.0",