Preventing XSS Attacks in Rich Text Editors: Concepts, Types, and Implementation
This article explains what XSS attacks are, describes the three main XSS types, and provides practical JavaScript functions for sanitizing and restoring rich‑text content to protect web applications from high‑risk cross‑site scripting vulnerabilities.
Introduction – Many front‑end projects use rich‑text editors, but they are often vulnerable to Cross‑Site Scripting (XSS) attacks. The author discovered that unfiltered rich‑text input in a recent project was flagged as a high‑severity XSS issue by the internal security team.
What is XSS? – XSS (Cross‑Site Scripting) is a common web security flaw where an attacker injects malicious scripts into a page that is viewed by other users. It occurs when a web application fails to properly filter user input, leading to script execution that can steal data, alter pages, or spread malware.
Three Main Types of XSS
Stored XSS (Stored XSS)
Malicious scripts are saved on the server (e.g., in a blog comment) and served to other users when they request the page.
Reflected XSS (Reflected XSS)
The script is delivered via a URL parameter or input field and executed immediately in the victim’s browser.
DOM‑based XSS (DOM‑based XSS)
The attack manipulates the Document Object Model on the client side using JavaScript, without involving server‑side code.
Rich‑text editors typically fall into the Stored XSS category because the malicious markup is saved in the database and rendered later.
How to Prevent XSS in Rich Text
Simple escaping of dangerous characters is often sufficient. When no sanitization is applied, the submitted rich‑text may contain tags like <script> or attributes such as src , which can execute scripts.
By converting characters such as < , > , & , " , ' , and \ to their HTML entities before storing, the risk is mitigated.
Below is a JavaScript utility that escapes these characters:
export const getXssFilter = (value: string): string => {
// Define a map of characters to HTML entities
const htmlEntities = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
'\'': ''',
'\\': '\',
'|': '|',
';': ';',
'$': '$',
'%': '%',
'@': '@',
'(': '(',
')': ')',
'+': '+',
'\r': '',
'\n': '',
',': ','
};
// Replace all special characters using a regular expression
let result = value.replace(/[&<>"'\\|;$%@()+,]/g, match => htmlEntities[match] || match);
return result;
};After sanitization, the stored content appears escaped, as shown in the following screenshot (omitted). To display the original formatting to users, the escaped content must be restored either on the front‑end or back‑end.
Front‑end restoration example:
// Restore escaped characters
export const setXssFilter = (input) => {
return input
.replace(/\|/g, '|')
.replace(/&/g, '&')
.replace(/;/g, ';')
.replace(/\$/g, '$')
.replace(/%/g, '%')
.replace(/@/g, '@')
.replace(/'/g, "'")
.replace(/"/g, '"')
.replace(/\\/g, '\\')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/\(/g, '(')
.replace(/\)/g, ')')
.replace(/\+/g, '+')
.replace(/\r/g, '')
.replace(/\n/g, '')
.replace(/,/g, ',');
};However, this generic restoration can re‑introduce XSS risks in plain‑text contexts. A more targeted approach is to escape only the characters and tags that pose a threat (e.g., script , iframe , object , embed , etc.) while leaving other formatting intact.
Enhanced filter example:
export const getXssFilter = (value: string): string => {
const htmlEntities = {
'&': '&',
'\'': ''',
'\r': '',
'\n': '',
'script': 'script',
'iframe': 'iframe',
// 'img': 'img',
'object': 'object',
'embed': 'embed',
'on': 'on',
'javascript': 'javascript',
'expression': 'expression',
'video': 'video',
'audio': 'audio',
'svg': 'svg',
'background-image': 'background-image'
};
// Escape special characters
let result = value.replace(/[&<>"'\\|;$%@()+,]/g, match => htmlEntities[match] || match);
// Additional handling for dangerous keywords
result = result.replace(/script|iframe|object|embed|on|javascript|expression|background-image/gi, match => htmlEntities[match] || match);
return result;
};This selective sanitization ensures that only high‑risk elements are neutralized, allowing the rich‑text formatting to remain visible without needing a full reversal step.
Conclusion
Escaping dangerous characters and selectively filtering risky tags provides a straightforward and effective way to protect rich‑text inputs from XSS attacks while preserving the intended display for end users.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.