RichText Widget Security Architecture
Defense-in-depth security architecture for the RichText widget with XSS protection, CSS injection prevention, and URL validation.
On this page
Overview
The RichText widget implements defense-in-depth security with multiple layers of XSS protection:
- Input Sanitization (input.svelte) - Sanitizes before database storage
- Output Sanitization (display.svelte) - Sanitizes before rendering
- Extension Hardening - Prevents CSS/attribute injection
- URL Validation - Blocks malicious URIs
Security Layers
1. Input Sanitization (Storage Protection)
File: src/widgets/core/rich-text/input.svelte
// Sanitizes HTML before storing to database
editor.on('update', () => {
let newContent = editor!.getHTML();
if (DOMPurify && newContent) {
newContent = DOMPurify.sanitize(newContent, {
ALLOWED_TAGS: ['p', 'strong', 'em', 'h1', 'a', 'img', ...],
ALLOWED_ATTR: ['href', 'src', 'alt', 'class', 'style', ...],
ALLOWED_URI_REGEXP: /^(?:https?:\/\/|mailto:|tel:|#|\/)/, // Blocks javascript:, data:
ALLOW_DATA_ATTR: false
});
}
value[lang].content = newContent;
});
Why: Prevents malicious content from ever reaching the database.
2. Output Sanitization (Render Protection)
File: src/widgets/core/rich-text/display.svelte
<Sanitize html={value.content} profile="rich-text" class="prose" />
Why: Even if sanitization failed at input (e.g., direct DB manipulation), output is still safe.
3. ImageResize Extension Hardening
File: src/widgets/core/rich-text/extensions/ImageResize.ts
✅ Fixed Vulnerabilities
| Attack Vector | Fix |
|---|---|
| Description XSS | Use textContent (auto-escapes) instead of innerHTML |
| CSS Injection (float) | Whitelist: ['left', 'right', 'unset', 'none'] |
| CSS Injection (width/height) | Regex validation: /^\d+(px\|%)$/ |
// BEFORE (vulnerable):
container.style.float = nodeAttrs.float as string; // Injects arbitrary CSS
// AFTER (secure):
const safeFloat = ["left", "right", "unset", "none"].includes(nodeAttrs.float as string)
? nodeAttrs.float
: "unset";
container.style.float = safeFloat;
4. TextStyle Extension Hardening
File: src/widgets/core/rich-text/extensions/TextStyle.ts
Font-Size Validation
// BEFORE (vulnerable):
setFontSize: (fontSize: string) => chain().setMark(this.name, { fontSize }).run();
// AFTER (secure):
setFontSize: (fontSize: string) => {
const sanitized = fontSize.match(/^\d+(\.\d+)?(px|em|rem|pt|%)$/) ? fontSize : "16px"; // Safe fallback
return chain().setMark(this.name, { fontSize: sanitized }).run();
};
Blocks:
fontSize: "10px; color: red; }"→ CSS injectionfontSize: "expression(alert(1))"→ IE CSS expressionsfontSize: "url('javascript:...')"→ URL-based injection
5. Video URL Validation
File: src/widgets/core/rich-text/components/VideoDialog.svelte
// Strict YouTube URL validation
const youtubePattern =
/^https:\/\/(www\.)?(youtube\.com\/watch\?v=|youtu\.be\/)([a-zA-Z0-9_-]{11})/;
if (!youtubePattern.test(youtube_url)) {
alert("Invalid YouTube URL");
return;
}
Blocks:
javascript:alert(1)data:text/html,<script>alert(1)</script>http://youtube.com(enforces HTTPS)https://evil.com/youtube.com/watch?v=...(strict domain check)
6. Link Extension Protocol Hardening ✅
File: src/widgets/core/rich-text/tiptap.ts
Vulnerability: The Tiptap Link extension was configured without protocol restrictions, allowing editors to create javascript: and data: URI links. While the display pipeline strips these at render time, the editor allowed their creation.
Fix: Added an explicit protocol allowlist: protocols: ["http", "https", "mailto", "tel"].
Protection: javascript:/data: URI blocking at editor level, defense-in-depth with sanitize-html.ts server-side and DOMPurify client-side.
Attack Surface Reduction
Without Sanitization Hardening
| Component | Vulnerabilities | Risk Level |
|---|---|---|
| input.svelte | No sanitization | 🔴 Critical |
| ImageResize | Description XSS, CSS injection | 🔴 Critical |
| TextStyle | CSS injection | 🟠 High |
| VideoDialog | URL injection | 🟠 High |
After Hardening
| Component | Protection | Risk Level |
|---|---|---|
| input.svelte | DOMPurify + strict whitelist | 🟢 Low |
| ImageResize | Attribute validation + textContent | 🟢 Low |
| TextStyle | Regex validation | 🟢 Low |
| VideoDialog | URL pattern matching | 🟢 Low |
Testing Attack Vectors
Test Suite (Manual QA)
<!-- Test 1: Script injection in description -->
<img src="x" alt="test" description="<script>alert(1)</script>" />
Expected: Description shows literal text, no execution
<!-- Test 2: CSS injection via float -->
<div style="float: expression(alert(1))">
Expected: Falls back to float: unset
<!-- Test 3: Font-size injection -->
<span style="font-size: 10px; color: red; }; body { background: red; ">
Expected: Falls back to font-size: 16px
<!-- Test 4: YouTube URL injection -->
https://evil.com/youtube.com/watch?v=abc Expected: Rejected with "Invalid YouTube URL" error
<!-- Test 5: JavaScript URI in YouTube -->
javascript:alert(document.cookie) Expected: Rejected (doesn't match pattern)</span
>
</div>
Best Practices for Developers
DO ✅
- Always use Sanitize component for
{@html}rendering - Validate URLs before inserting into editor
- Whitelist attributes in custom extensions
- Use textContent for user-provided text (auto-escapes)
- Test with OWASP XSS vectors before deploying
DON’T ❌
- Don’t trust Tiptap’s built-in sanitization (it’s minimal)
- Don’t use innerHTML with user content
- Don’t allow arbitrary style attributes without validation
- Don’t skip output sanitization (defense-in-depth)
- Don’t use regex for HTML parsing (use DOMPurify)
Compliance
This implementation meets:
- ✅ OWASP Top 10 (A03:2021 Injection)
- ✅ CWE-79 (Cross-site Scripting)
- ✅ CWE-83 (Improper Neutralization of Script in Attributes)
- ✅ CSP Level 3 (when used with strict CSP)
References
- DOMPurify Documentation
- OWASP XSS Prevention Cheat Sheet
- Tiptap Security Best Practices
- Content Security Policy Guide
Emergency Response
If you discover a security vulnerability:
- DO NOT open a public GitHub issue
- Email: info@sveltycms.com
- Include: Proof of concept, impact assessment, suggested fix
- Response SLA: 24 hours for critical, 72 hours for high severity