Overview
Because contenteditable editors work directly
with HTML, they are inherently susceptible to
Cross-Site Scripting (XSS). If an attacker can
inject a script tag or an event handler (like onload or onerror) into your document,
they can execute arbitrary code in your users'
browsers.
XSS Risks
Common attack vectors in editors include:
- Paste: User pastes HTML containing
<img src=x onerror=alert(1)>. - Data Loading: Loading compromised JSON/HTML from the server.
- Drag & Drop: Dropping malicious HTML fragments.
Sanitization Strategy
Never trust HTML. Always sanitize it before inserting it into the DOM. We recommended using DOMPurify.
import DOMPurify from 'dompurify';
// Configure allowed tags and attributes
const config = {
ALLOWED_TAGS: ['p', 'b', 'i', 'em', 'strong', 'a', 'ul', 'ol', 'li'],
ALLOWED_ATTR: ['href', 'target', 'class'],
FORBID_TAGS: ['script', 'style', 'iframe', 'object'],
FORBID_ATTR: ['onclick', 'onerror', 'onmouseover']
};
export function sanitize(html: string) {
return DOMPurify.sanitize(html, config);
}Safe Paste Handling
When extracting content from the clipboard, use DOMParser to parse it into an inert document, then sanitize
or transform it into your model immediately.
Warning: innerHTML
Never assign pasted text directly to innerHTML without sanitization. Even assigning it
to a detached element can trigger network
requests (e.g., <img src="...">).
Trusted Types
Trusted Types is a browser security
feature that prevents DOM XSS by enforcing that only
policy-verified strings can be assigned to dangerous
sinks (like innerHTML).
// 1. Create a policy
const escapePolicy = trustedTypes.createPolicy('my-editor-policy', {
createHTML: (string) => DOMPurify.sanitize(string)
});
// 2. Use the policy
const safeHTML = escapePolicy.createHTML('<p>Potentially unsafe</p>');
element.innerHTML = safeHTML;