
Total Views
378
Read Time
30 min read
Updated On
22.10.2026
Introduction
Rich Text Editor CSP Configuration Guide 2026 (Strict Headers That Work)
Rich text editor CSP configuration 2026: strict Level 3 headers for Eddyter, TipTap, CKEditor 5, Quill. Nonces, hashes, strict-dynamic, Trusted Types, working code for Next.js, Nuxt, Django, Rails.
TL;DR
Rich text editor CSP 2026: use nonces + strict-dynamic + Trusted Types for XSS defense. Ship Level 3 headers that pass Google CSP Evaluator. Eddyter, TipTap, CKEditor 5, Quill all work. Real code.

Content
Rich Text Editor CSP Configuration Guide 2026 (Strict Headers That Work)
Getting rich text editor CSP configuration right in 2026 is harder than it should be. Every modern editor injects inline styles. Some inject inline scripts. Many load fonts from a CDN. All of this trips a strict Content Security Policy and breaks the editor on load. Your security team asks for CSP Level 3. Your product team asks for a working editor. Both are right. Both need to happen.
This guide shows you exact rich text editor CSP configuration code that keeps strict CSP AND a working editor. You'll see the full Level 3 headers, nonces, hashes, strict-dynamic, and Trusted Types. You'll get framework-by-framework setup for Next.js 15, Nuxt 3, Django, Rails, Laravel, and Express. You'll get editor-by-editor rules for Eddyter, TipTap, CKEditor 5, Quill, and Lexical.
The rich text editor CSP configuration pattern that works in 2026 is a nonce-based Level 3 CSP with strict-dynamic, no unsafe-inline for scripts, careful style-src handling for editor styles, and Trusted Types for extra XSS defense. This post shows you the code, explains each part in plain English, and lists every gotcha we've hit shipping editors into apps with strict CSP.
The short answer: Use a fresh nonce per request. Add 'strict-dynamic' to script-src. Allow 'unsafe-inline' on style-src only (editors need inline styles). Set up Trusted Types. Ship in report-only mode first. Watch reports for a week. Then enforce.
🎥 New to Eddyter? Watch: What is Eddyter? Why Developers Are Switching in 2026
What Is CSP, in Plain English?
CSP stands for Content Security Policy. It's a header your server sends with each page. The header tells the browser what code and content it can load — and what it must block.
You use it to stop XSS attacks. If a hacker injects a <script> tag into your page, a strict CSP blocks that script from running. No CSP means the injected script runs, steals cookies, and drains user accounts.
CSP has three levels. Level 1 shipped in 2012. Level 2 in 2016. Level 3 in 2023 (finalized). Level 3 is what modern browsers use in 2026. It adds strict-dynamic, 'unsafe-hashes', and Trusted Types — all key for a strict but usable CSP.
The Three Parts of a Good CSP
script-src— controls which scripts can run. This is the most important rule.style-src— controls which CSS can load. This is where rich text editors get tricky.default-src— the catch-all fallback for anything not listed above.
You'll set more directives too (img-src, font-src, connect-src, etc.), but these three are the core.
Why Editors Break Strict CSP
Rich text editors are one of the hardest tools to ship under strict CSP. Here's why.
1. Editors Inject Inline Styles
Every modern editor writes inline styles to the DOM. When you resize an image, the editor sets style="width: 400px" on that image. When you drag a table column, it writes new widths inline. Blocks like TipTap, ProseMirror, Lexical, and CKEditor all do this.
A strict style-src 'self' blocks these inline styles. The editor still renders — but resize breaks, drag-drop breaks, and layout looks wrong. You must allow 'unsafe-inline' on style-src for most editors to work.
2. Some Editors Inject Inline Scripts
Older editors like TinyMCE and CKEditor 4 sometimes inject inline <script> tags. Modern editors (Lexical, TipTap, Eddyter) avoid this. But third-party plugins might not.
A strict script-src with no 'unsafe-inline' blocks these. Test every plugin.
3. Editors Load Fonts and Images From CDNs
Editor icons, custom fonts, and syntax highlighting themes often load from a CDN. img-src and font-src must allow those hosts. Miss one and you get broken icons.
4. AI Editors Call External APIs
AI-native editors like Eddyter, Novel, and TipTap AI Toolkit call OpenAI, Anthropic, or Google endpoints. connect-src must allow those hosts.
5. Real-Time Collaboration Uses WebSockets
RTC editors open a WebSocket to a Y.js or Liveblocks server. connect-src must allow wss:// for that host.
For deeper RTC context, see How to Add Real-Time Collaboration to a React Editor With Y.js 2026.
The Strict CSP That Works With Every Modern Editor
Here's the CSP header that works with Eddyter, TipTap, CKEditor 5, Lexical, Quill, and BlockNote in 2026:
Let's break this down line by line so you know what each part does.
default-src 'self'
Only load content from your own domain by default. This is the safety net for anything not listed below.
script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic'
Scripts must come from your own domain OR carry a nonce that matches the header. 'strict-dynamic' tells the browser: "If a script loaded via nonce loads another script, trust that too." This lets modern bundlers work without listing every chunk hash.
No 'unsafe-inline' here. That's the whole point of strict CSP.
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com
Styles come from your own domain, inline styles are allowed (editors need this), and Google Fonts CSS can load. 'unsafe-inline' weakens CSP a bit for styles — but it's the reality of shipping editors in 2026.
If you don't use Google Fonts, drop that host.
img-src 'self' data: blob: https:
Images from your own domain, data URIs (for uploaded images before they hit the CDN), blob URIs (for drag-drop uploads), and any HTTPS URL (for embedded images from users).
If you want tighter control, list only your CDN and image hosts here.
font-src 'self' https://fonts.gstatic.com
Fonts from your own domain and Google Fonts static files. Adjust for your font provider.
connect-src 'self' https://api.eddyter.com wss://collab.eddyter.com
Fetch and WebSocket connections. Include:
- Your API host
- Your editor's API host (e.g.,
api.eddyter.com) - Your RTC server host (WebSocket)
- Any AI API host (e.g.,
api.openai.comif you use OpenAI directly)
frame-src 'self' https://www.youtube.com
Which sites can be loaded in iframes. If your editor supports YouTube embeds, allow YouTube here. Same for Vimeo, Loom, Figma, etc.
object-src 'none'
Block all <object>, <embed>, and <applet> tags. Almost no site needs these. Blocking them stops a whole class of XSS attacks.
base-uri 'self'
Stop attackers from injecting <base> tags that redirect relative URLs. Always set this.
form-action 'self'
Only allow form submissions to your own domain. Stops phishing forms.
frame-ancestors 'none'
No other site can iframe your page. Stops clickjacking. Replaces the old X-Frame-Options: DENY header.
require-trusted-types-for 'script'
Force all DOM sinks that create scripts to go through a Trusted Types policy. This blocks a huge class of DOM-based XSS.
trusted-types default eddyter-policy
Define which Trusted Types policies your app uses. Chrome and Edge honor this. Firefox and Safari support is growing in 2026.
report-uri /api/csp-report
Send CSP violation reports to this endpoint. You'll use this to catch problems before they break users.
Nonces: What They Are and How to Use Them
A nonce is a one-time random value. You generate it fresh on every request. You put it in the CSP header AND on every <script> tag you trust.
The browser only runs scripts whose nonce matches the header. Injected scripts have no nonce — so they get blocked.
How to Generate a Nonce
Node.js example:
typescript
The nonce must be:
- Fresh per request (never reused)
- At least 128 bits of entropy (16 bytes)
- Base64-encoded
How to Use a Nonce
Add the nonce to your CSP header and to every trusted <script> tag:
typescript
Any <script> without the matching nonce won't run. That's how nonce-based CSP blocks injected scripts.
Hashes: The Alternative to Nonces
If you can't use nonces (say, on a fully static site), use hashes instead. A hash is a SHA-256 fingerprint of a specific inline script.
You compute the hash once. You add it to your CSP header. The browser only runs inline scripts whose SHA-256 hash matches.
Example header:
Nonces are easier for dynamic pages. Hashes are better for static sites where every script is known ahead of time.
strict-dynamic: The Modern CSP Magic
'strict-dynamic' is the single most important CSP Level 3 feature. It says: "Any script I trust (via nonce or hash) can load more scripts. Trust those too."
Without strict-dynamic, you'd need to list every script your bundler outputs — every chunk, every dynamic import, every third-party dependency. That's a maintenance nightmare.
With strict-dynamic, you trust the entry script (via nonce). It loads more scripts. Those scripts load even more scripts. The browser trusts the whole chain.
Add 'strict-dynamic' to script-src in every modern CSP.
Trusted Types: Extra XSS Defense
Trusted Types is a browser feature that stops DOM-based XSS at the source. It blocks any attempt to write untrusted strings to DOM sinks like innerHTML, insertAdjacentHTML, or document.write.
You force this with require-trusted-types-for 'script' in your CSP.
Setting Up a Trusted Types Policy for Editors
Rich text editors write HTML to the DOM constantly. You need a Trusted Types policy that sanitizes editor output first.
typescript
Every string that flows into a DOM sink now passes through DOMPurify first. XSS payloads get stripped. Editor content still renders correctly.
Framework-by-Framework Setup
Here's how to ship this CSP in the six most common web frameworks in 2026.
1. Next.js 15 (App Router)
Use middleware to generate a fresh nonce per request:
typescript
Read the nonce from your Server Component and pass it to your Script tags:
tsx
2. Nuxt 3
Use Nitro middleware:
typescript
3. Django 5
Use django-csp:
python
django-csp handles nonce generation and injection into <script> tags automatically.
4. Ruby on Rails 8
Rails ships CSP support out of the box:
ruby
Rails auto-adds the nonce to <%= javascript_include_tag %> and inline scripts.
5. Laravel 11
Add a middleware:
php
Register it in bootstrap/app.php:
php
Read the nonce in Blade templates:
blade
6. Express (Node.js)
Use helmet:
typescript
Editor-by-Editor CSP Rules
Each editor has quirks. Here's what to add to your CSP for each one.
Eddyter
Eddyter needs:
'nonce-{RANDOM}'+'strict-dynamic'— Eddyter's bundle loads chunks dynamically'unsafe-inline'onstyle-src— Eddyter writes inline styles for images, tables, blocksapi.eddyter.comfor editor licensing + AI featureswss://collab.eddyter.comfor real-time collaboration (Q3 2026 launch)data:andblob:for uploaded images- Add your CDN host to
img-srcif you use one
Basic Eddyter setup that respects CSP:
tsx
Get your API key at eddyter.com/user/license-key.
TipTap
TipTap needs:
- Same inline style needs as Eddyter (TipTap uses ProseMirror under the hood)
api.tiptap.devif you use TipTap Cloud- Add TipTap AI Toolkit endpoints if you enable it
- Add Y.js WebSocket host if you enable Collaboration
For deeper analysis, see Eddyter vs TipTap 2026.
CKEditor 5
CKEditor 5 needs:
*.cke-cs.comfor CKEditor Cloud Services (comments, track changes, RTC)- Inline styles for resizing, table editing, image alignment
- If you self-host, drop the CKEditor Cloud hosts
For deeper analysis, see Eddyter vs CKEditor 2026.
Quill
Quill needs:
- Same basic needs
- No cloud API host (Quill is fully self-hosted)
- Add any image upload CDN host
Lexical
Lexical needs:
- Lexical is a framework, not a hosted editor
- No external hosts required unless you add plugins that call APIs
- If you build custom AI on Lexical, add your AI API host to
connect-src
For deeper analysis, see Best Lexical Alternative 2026.
The style-src Problem: Why You Can't Skip 'unsafe-inline'
Rich text editors write inline styles constantly. Resize an image, and the editor sets style="width: 400px; height: 300px". Drag a table column, and it writes new widths inline. Change a color, and inline color: shows up.
You can't ship an editor without allowing this.
Options for Tighter style-src
You have three paths:
- Allow
'unsafe-inline'(recommended) — the practical path for 2026 - Use hashes for every possible inline style — impossible in practice
- Use
'unsafe-hashes'with a nonce — CSP Level 3 experimental, limited browser support
Most teams ship with option 1 today. That's the honest recommendation.
What About style-src-attr and style-src-elem?
CSP Level 3 splits style-src into two directives:
style-src-elem— controls<style>blocks and<link rel="stylesheet">style-src-attr— controls inlinestyle="..."attributes
You can allow 'unsafe-inline' only on style-src-attr (which editors need) while keeping style-src-elem strict:
This is the best-of-both compromise. It's the tightest CSP an editor can run under while still working.
Trusted Types Policy for Editors
Rich text editors write HTML to innerHTML constantly. Trusted Types blocks this unless you set up a policy.
Here's a policy that sanitizes editor output with DOMPurify:
typescript
Add this to your app entry point. Every string that flows to a DOM sink now passes through DOMPurify.
For deeper XSS defense, see Rich Text Editor Security Best Practices 2026.
Common CSP Errors and How to Fix Them
Here are the CSP errors you'll hit shipping an editor, and the fix for each.
Error: "Refused to execute inline script"
Cause: A script tag with no nonce.
Fix: Add the current request's nonce to every trusted <script> tag. Never use inline handlers like onclick="doThing()" — move them to event listeners.
Error: "Refused to apply inline style"
Cause: The editor tried to write style="..." and your CSP blocks inline styles.
Fix: Add 'unsafe-inline' to style-src (or style-src-attr for CSP Level 3).
Error: "Refused to load the image because it violates CSP"
Cause: The editor tried to load an image from a host you didn't allow.
Fix: Add the host to img-src. For user-uploaded images, add data: and blob:.
Error: "Refused to connect to WebSocket"
Cause: Your RTC or AI feature opens a WebSocket to a host not in connect-src.
Fix: Add wss://your-rtc-host.com to connect-src.
Error: "This document requires 'TrustedHTML' assignment"
Cause: You enabled Trusted Types but haven't set up a policy.
Fix: Create a default policy or use a named policy (see the DOMPurify setup above).
Error: "Refused to load font"
Cause: The editor loads a font from a host not in font-src.
Fix: Add the font CDN host to font-src.
Error: "Refused to frame"
Cause: The editor embeds YouTube, Vimeo, or another iframe from a host not in frame-src.
Fix: Add the embed host to frame-src.
Report-Only Mode: Ship Safely
Never enforce a strict CSP on day one. You'll break the editor in ways you didn't test for. Use report-only mode first.
Change the header name:
The browser applies the CSP but only reports violations. Nothing gets blocked. Set up a /api/csp-report endpoint to catch reports:
typescript
Ship in report-only mode for a week. Watch reports. Fix each violation. Then flip to enforcement mode.
Testing Your CSP
Test your CSP before shipping. Two tools you should use:
1. Google CSP Evaluator
Visit csp-evaluator.withgoogle.com. Paste your CSP. It grades each directive and flags weak spots.
Aim for:
- ✅
script-srcuses nonce +'strict-dynamic' - ✅ No
'unsafe-inline'onscript-src - ✅
object-src 'none' - ✅
base-uri 'self'or'none' - ✅
frame-ancestorsset
'unsafe-inline' on style-src will get flagged — that's expected for editors.
2. Chrome DevTools
Open DevTools → Console. Try the editor. Watch for red CSP violation errors. Each one names the directive it violated. Fix them one at a time.
When You Don't Need a Strict CSP
Two scenarios where strict CSP isn't worth the setup cost:
1. Internal Tools With No External Users
If your editor only runs inside your company on a VPN, threat model is different. Basic CSP is fine.
2. Static Marketing Sites
If your site only has a blog and marketing pages (no user content, no forms), CSP is nice-to-have but not urgent.
For any editor that accepts user content — SaaS, forums, CMS, comment systems — strict CSP is mandatory in 2026.
Frequently Asked Questions
1. What's the best rich text editor CSP configuration in 2026?
The best rich text editor CSP configuration in 2026 uses nonce-based CSP Level 3 with 'strict-dynamic' on script-src, 'unsafe-inline' on style-src (editors need it), and Trusted Types for extra XSS defense. Full header: default-src 'self'; script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self'; connect-src 'self' https://your-api.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none'; require-trusted-types-for 'script';. This works with Eddyter, TipTap, CKEditor 5, Lexical, Quill, and BlockNote. Ship in report-only mode first. Watch reports for a week. Then enforce.
2. Can rich text editors work with strict CSP?
Yes, but with one compromise: you must allow 'unsafe-inline' on style-src. Every modern rich text editor writes inline styles for image resize, table column widths, alignment, and colors. There's no way around this in 2026. You keep the strong parts of strict CSP (no inline scripts, strict-dynamic, Trusted Types) while making one practical concession for editor styles. For an even tighter version, use CSP Level 3's style-src-elem (strict) and style-src-attr 'unsafe-inline' (for editor needs) — the best-of-both compromise. This works with Eddyter, TipTap, CKEditor 5, Lexical, and Quill.
3. Do I need Trusted Types for rich text editors?
Yes, if you can enable it. Trusted Types is the strongest XSS defense in 2026. It blocks any attempt to write untrusted strings to DOM sinks like innerHTML. Set up require-trusted-types-for 'script' in your CSP. Create a Trusted Types policy that runs editor output through DOMPurify before writing it to the DOM. Chrome and Edge support Trusted Types today. Firefox and Safari support has grown in 2026. Even without full cross-browser support, Trusted Types cuts your XSS surface massively on Chromium-based browsers — where most users are.
4. What's the difference between CSP nonces and hashes?
Nonces are one-time random values you generate fresh per request. Hashes are SHA-256 fingerprints of specific inline scripts. Use nonces when your pages render dynamically (Next.js, Rails, Django, Laravel). Use hashes when your site is fully static and every inline script is known ahead of time. Nonces are easier to maintain — you don't recompute hashes every time an inline script changes. Both work equally well for security. Never use 'unsafe-inline' alongside a nonce — the nonce takes precedence in CSP Level 3, but the mixed signal confuses your CSP evaluator scores.
5. Why do editors need 'unsafe-inline' on style-src?
Rich text editors write inline styles to the DOM whenever a user resizes an image, drags a table column, changes text color, or aligns a block. These styles get set as style="..." attributes on elements. Strict CSP with style-src 'self' blocks all inline styles — which breaks resize, drag-drop, and alignment. You must allow 'unsafe-inline' on style-src for editors to work. The alternative (hash every possible inline style) is impossible in practice. In CSP Level 3, you can narrow this to style-src-attr 'unsafe-inline' only — keeping style-src-elem strict. That's the tightest working setup for editors.
6. How do I set up CSP for a Next.js 15 app with a rich text editor?
Use middleware to generate a fresh nonce per request. Set the CSP header with script-src 'self' 'nonce-${nonce}' 'strict-dynamic'; style-src 'self' 'unsafe-inline'; connect-src 'self' https://api.eddyter.com;. Pass the nonce to <Script> tags via headers. Middleware runs on every request, so the nonce stays fresh. Full working example is in the "Framework-by-Framework Setup" section above. Test in report-only mode first with Content-Security-Policy-Report-Only. Log violations. Fix each one. Then flip to enforcement. For deeper Next.js 15 patterns, see Rich Text Editor Migration Checklist 2026.
7. Does Eddyter work with strict CSP?
Yes. Eddyter ships with strict CSP support built in. Add 'nonce-{RANDOM}' 'strict-dynamic' to script-src, allow 'unsafe-inline' on style-src (needed for image resize and table editing), add https://api.eddyter.com to connect-src for API access, and add wss://collab.eddyter.com if you use Q3 2026 real-time collaboration. Eddyter doesn't inject inline scripts. Eddyter's bundle uses modern chunk-loading that works with 'strict-dynamic'. Full working CSP header for Eddyter is in the "Editor-by-Editor CSP Rules" section above. Get your Eddyter API key at eddyter.com/user/license-key.
8. Should I use CSP report-only mode or enforcement mode?
Start with report-only. Ship a strict CSP with the header name Content-Security-Policy-Report-Only. The browser applies the policy but only reports violations — nothing gets blocked. Set up a /api/csp-report endpoint to catch reports. Log every violation. Watch for a week. Fix each violation you didn't expect. Then rename the header to Content-Security-Policy for enforcement mode. This two-stage rollout catches every gotcha before it breaks users. Never enforce a strict CSP on day one — you'll break the editor in ways you didn't test for. The report-only phase costs you nothing and saves you angry support tickets.
9. What CSP directives are mandatory for rich text editors?
Six directives are mandatory: default-src 'self' (safety net), script-src 'self' 'nonce-{RANDOM}' 'strict-dynamic' (main XSS defense), style-src 'self' 'unsafe-inline' (editors need inline styles), img-src 'self' data: blob: https: (uploaded and embedded images), connect-src 'self' <your-api-host> (API and AI calls), and object-src 'none' (block a whole class of attacks). Four more directives are strongly recommended: base-uri 'self' (stop base-tag hijacks), form-action 'self' (stop phishing forms), frame-ancestors 'none' (stop clickjacking), and require-trusted-types-for 'script' (DOM-based XSS defense). The full recommended CSP header is in the "The Strict CSP That Works" section above.
10. How do I debug CSP violations from my rich text editor?
Open Chrome DevTools → Console. Each CSP violation shows up as a red error with the exact directive that got violated. Fix them one at a time. Common patterns: "Refused to apply inline style" means add 'unsafe-inline' to style-src. "Refused to load image" means add the host to img-src (or data:/blob: for uploads). "Refused to connect" means add the host to connect-src. Use Google CSP Evaluator at csp-evaluator.withgoogle.com to grade your full CSP. Set up report-only mode with a /api/csp-report endpoint to catch violations you missed in testing. Log every report. Fix the pattern, then enforce.
Ready to Ship a Secure Rich Text Editor?
Strict CSP is table stakes in 2026. Every SaaS handling user content needs it. Every editor you ship needs to work under it. This guide gives you the exact headers, the framework code, the editor-specific rules, and the testing plan.
For an editor that works with strict CSP out of the box — no XSS to sanitize, no inline scripts to fingerprint, no cloud services to allowlist beyond one host — Eddyter is the fastest path in 2026.
👉 Try Eddyter free at eddyter.com
📚 Read the docs
💰 See pricing
🎥 Watch the intro video | Watch the 30-min integration guide

Written by
Shreya Taneja
Project Manager

