Security best practices for using React Advanced Rich Text Editor
Security is a shared responsibility between the editor and your backend. This guide details the protections that ship out of the box—DOMPurify sanitization, tag allowlists, safe paste—and shows you how to layer additional defenses like server-side validation, CSP headers, storage policies, and threat modeling. Use it as a checklist when launching new surfaces or reviewing compliance requirements.
Overview
The editor includes built-in security features to protect against XSS attacks and other security vulnerabilities. However, proper implementation and additional server-side validation are essential.
- Client guardrails: DOMPurify sanitizes every change, copy/paste is scrubbed, and toolbar options honor the
allowedTagswhitelist. - Server responsibilities: Re-sanitize, validate URLs/hosts, and enforce storage rules because browsers can be bypassed.
- Defense in depth: Combine CSP headers, iframe sandboxing (if embedding), rate limits, and security logging for high-risk deployments.
Built-in Security Features
1. DOMPurify Integration
All HTML content is automatically sanitized using DOMPurify, which:
- Removes potentially dangerous HTML
- Sanitizes attributes
- Prevents XSS attacks
- Cleans pasted content from external sources
// Content is automatically sanitized
<Editor value={content} onChange={setContent} />DOMPurify runs in STRICT mode with a curated whitelist. If you add custom plugins that inject new attributes (e.g., tracking IDs), extend the sanitizer config on both client and server so content stays consistent.
2. Allowed Tags Control
Restrict which HTML tags can be used in the editor:
// Only allow safe tags
<Editor
value={content}
onChange={setContent}
allowedTags={[ // Restrict allowed HTML tags
'p', 'h1', 'h2', 'h3',
'strong', 'em', 'u',
'ul', 'ol', 'li',
'a', 'img'
]}
/>Use granular lists per surface. Comments might only allow inline tags, while knowledge bases can include tables and media. Keep the lists in shared constants so client and server stay aligned.
3. Safe Paste
Pasted content is automatically cleaned:
- Removes dangerous scripts
- Sanitizes attributes
- Cleans Word document formatting
- Removes event handlers
Smart quotes are normalized, tracking pixels removed, and nested spans collapsed. For strict environments hook into onPaste to block external images or rewrite URLs before they land in state.
Security Best Practices
1. Server-Side Validation
Always validate and sanitize content on the server before storing or displaying:
// Server-side example (Node.js)
const DOMPurify = require('isomorphic-dompurify');
app.post('/api/content', (req, res) => {
const { content } = req.body; // Get HTML content from request
// Sanitize on server - CRITICAL for security
const sanitized = DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['p', 'h1', 'h2', 'strong', 'em'], // Whitelist allowed tags
ALLOWED_ATTR: ['href', 'src', 'alt'] // Whitelist allowed attributes
});
// Store sanitized content in database
saveToDatabase(sanitized);
});2. Restrict Allowed Tags
Use the allowedTags prop to limit HTML tags based on your use case:
// Minimal tags for simple text
<Editor
allowedTags={['p', 'strong', 'em', 'br']}
/>
// Rich content with images and links
<Editor
allowedTags={[
'p', 'h1', 'h2', 'h3',
'strong', 'em', 'u',
'ul', 'ol', 'li',
'a', 'img', 'table',
'thead', 'tbody', 'tr', 'th', 'td'
]}
/>3. Content Security Policy (CSP)
Implement Content Security Policy headers:
Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';Tighten the policy for production by whitelisting only critical CDNs (fonts, icons) and—if policy allows—replacing 'unsafe-inline' with nonce-based scripts or 'strict-dynamic'.
4. Validate on Display
When displaying editor content, always use React’s dangerouslySetInnerHTML carefully:
// ✅ Good: Content is already sanitized by editor
<div dangerouslySetInnerHTML={{ __html: content }} />
// ⚠️ Better: Sanitize again before display
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{
__html: DOMPurify.sanitize(content)
}} />5. Disable Auto-Save for Sensitive Content
For sensitive content, disable localStorage auto-save:
<Editor
storageKey="" // Disable auto-save
value={content}
onChange={setContent}
/>Also consider encrypting drafts or falling back to sessionStorage when compliance rules demand purging content once the tab closes.
Common Security Risks
1. XSS (Cross-Site Scripting)
Risk: Malicious scripts injected into content
Protection:
- DOMPurify automatically removes scripts
- Allowed tags restriction prevents script tags
- Server-side validation required
- Encode user HTML before forwarding it to third-party systems to avoid double-decoding attacks
2. HTML Injection
Risk: Unwanted HTML elements
Protection:
- Use
allowedTagsprop - Server-side tag whitelisting
- Regular content validation
- Rewrite unsupported attributes (e.g., inline event handlers) before persisting
3. Link Spoofing
Risk: Malicious links in content
Protection:
- Validate URLs on server
- Use
rel="noopener noreferrer"(automatically added) - Check link destinations
- Add analytics or warning banners for external domains in regulated environments
Security Checklist
- Use
allowedTagsto restrict HTML tags - Sanitize content on the server before storing
- Validate content before displaying
- Implement Content Security Policy
- Disable auto-save for sensitive content
- Validate URLs in links and images
- Regular security audits
- Keep dependencies updated
- Monitor for security vulnerabilities
- Test with malicious input
- Log editing actions (create/update/delete) when compliance requires audit trails
- Back up sanitized content and optionally store raw submissions for forensic review
Example: Secure Implementation
import { useState } from 'react';
import Editor from '@webbycrown/react-advanced-richtext-editor';
import DOMPurify from 'dompurify';
function SecureEditor() {
const [content, setContent] = useState('');
// Sanitize before saving
const handleSave = async () => {
const sanitized = DOMPurify.sanitize(content, {
ALLOWED_TAGS: ['p', 'h1', 'h2', 'strong', 'em', 'ul', 'ol', 'li'],
ALLOWED_ATTR: []
});
// Send to server
await fetch('/api/content', {
method: 'POST',
body: JSON.stringify({ content: sanitized })
});
};
return (
<>
<Editor
value={content}
onChange={setContent}
allowedTags={['p', 'h1', 'h2', 'strong', 'em', 'ul', 'ol', 'li']}
storageKey="" // Disable auto-save
/>
<button onClick={handleSave}>Save</button>
</>
);
}Pattern recap: sanitize at every boundary (client before send, server before store, client before render) and keep allowedTags synchronized so content isn’t stripped unexpectedly.