CodeMirror 6 is much friendlier to Content Security Policy than a lot of frontend tooling. That said, I still see people break their CSP the moment they add an editor, especially when they cargo-cult old CodeMirror 5 examples or loosen the policy until the errors disappear.
That’s the wrong move.
If you’re using CodeMirror 6, you can usually keep a pretty tight CSP. Most of the pain comes from a few repeat mistakes: allowing the wrong directives, mixing up CodeMirror 5 and 6 behavior, or forgetting that your app around the editor has its own CSP needs.
Here are the mistakes I run into most often, and how I’d fix them.
Mistake #1: Assuming CodeMirror 6 needs unsafe-eval
A lot of JavaScript libraries used to need unsafe-eval somewhere in the stack, so developers add it preemptively. With CodeMirror 6, that’s usually unnecessary.
Bad policy:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'unsafe-eval';
style-src 'self' 'unsafe-inline';
That policy is weaker than it needs to be, and in many setups both exceptions are there “just in case”.
A better starting point for CodeMirror 6 looks more like this:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none';
If your app breaks with that, verify what actually failed before adding exceptions. Open the browser console and read the CSP violation. Don’t guess.
If you want a directive-by-directive refresher, the official CSP docs and the breakdowns at https://csp-guide.com are useful.
Mistake #2: Copying CodeMirror 5 CSP advice into a CodeMirror 6 app
This one wastes a lot of time.
CodeMirror 5 often triggered CSP workarounds because of how addons, modes, or styling were integrated in older apps. CodeMirror 6 is a redesign. Different package structure, different extension model, different assumptions.
I’ve seen teams add this because “CodeMirror needs it”:
style-src 'self' 'unsafe-inline';
script-src 'self' 'unsafe-eval';
Then nobody remembers why six months later.
If you’re on CodeMirror 6, test CodeMirror 6 directly under a strict policy. Don’t inherit old exceptions from an earlier editor build.
A minimal example with CodeMirror 6 can be perfectly happy under a strict CSP:
import {EditorView, basicSetup} from "codemirror"
import {javascript} from "@codemirror/lang-javascript"
const view = new EditorView({
doc: "console.log('hello')",
extensions: [basicSetup, javascript()],
parent: document.querySelector("#editor")
})
That doesn’t mean your whole app is CSP-clean. It means the editor itself probably isn’t the part forcing weak directives.
Mistake #3: Blaming CodeMirror for inline styles created by your framework
This is probably the most common misunderstanding.
You add CodeMirror 6 to a React, Vue, or Svelte app. The browser reports a style-src violation. Someone says “CodeMirror injects styles, so we need unsafe-inline”. Then the policy gets weakened.
Sometimes the editor is not the culprit at all. Your application shell, component library, CSS-in-JS runtime, or dev tooling may be injecting inline <style> blocks or style="" attributes.
The fix is to isolate the issue.
Start with a tiny page using only CodeMirror 6 and your production CSP:
<div id="editor"></div>
<script type="module" src="/assets/editor.js"></script>
If that works, the CSP problem lives elsewhere.
Also remember that production and development behave differently. Dev servers often use injected scripts, hot reload code, and inline styles. Don’t design your production CSP around dev-mode behavior.
Mistake #4: Using unsafe-inline for styles because “the editor needs styling”
This one is avoidable more often than people think.
A lot of apps end up with a policy like the real-world header from headertest.com:
content-security-policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-MTY3NWNjZWMtMmJmMi00NzgzLTgyZGQtMjczZjZhMmQyNmU0' 'strict-dynamic' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
style-src 'self' 'unsafe-inline' https://www.googletagmanager.com https://*.cookiebot.com https://consent.cookiebot.com;
img-src 'self' data: https:;
font-src 'self';
connect-src 'self' https://api.headertest.com https://tallycdn.com https://or.headertest.com wss://or.headertest.com https://*.google-analytics.com https://*.googletagmanager.com https://*.cookiebot.com;
frame-src 'self' https://consentcdn.cookiebot.com;
frame-ancestors 'none';
base-uri 'self';
form-action 'self';
object-src 'none'
That style-src 'unsafe-inline' is common in real deployments, but I wouldn’t treat it as a requirement for CodeMirror 6. It usually reflects the broader app stack: consent managers, tag managers, or UI libraries.
If you can avoid inline styles, do it.
Better:
style-src 'self';
If some trusted part of your app truly needs inline styles, prefer nonces or hashes where supported by your rendering model instead of opening the door globally.
For example, a nonce-based setup for inline style blocks:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-rAnd0m123';
style-src 'self' 'nonce-rAnd0m123';
<style nonce="rAnd0m123">
.app-shell { display: grid; }
</style>
That still beats unsafe-inline.
Mistake #5: Forgetting connect-src when the editor is embedded in a real app
CodeMirror 6 itself can run without network access. Your app probably can’t.
Autocomplete, linting, save-drafts, collaboration, AI hints, telemetry, websocket sync — these are where CSP usually starts failing. Developers look at the editor because that’s what’s visible on the screen, but the actual blocked resource is an API call.
A realistic policy might need something like:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
connect-src 'self' https://api.example.com wss://collab.example.com;
img-src 'self' data:;
font-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none';
If your CodeMirror integration uses language servers, lint endpoints, or collaboration backends, check connect-src first.
I’ve seen people add script-src https://api.example.com trying to fix a failed fetch(). Wrong directive, wrong fix.
Mistake #6: Loading themes or extensions from random CDNs
This is less a CodeMirror problem and more a supply-chain habit.
Someone prototypes with CDN imports:
<script type="module">
import {EditorView, basicSetup} from "https://cdn.example.com/codemirror.js"
</script>
Now your CSP needs to trust third-party script origins, and you’ve widened your attack surface for no good reason.
For a production deployment, bundle your CodeMirror 6 packages and serve them from your own origin.
Better CSP:
script-src 'self';
style-src 'self';
Better app architecture too.
If you absolutely must use third-party origins, scope them narrowly and document why they’re there. “Needed for editor” is not documentation.
Mistake #7: Allowing broad directives because of analytics, then assuming the editor is covered
The headertest.com header is a good example of how real CSPs grow over time. You start with a tight policy, then marketing tools show up:
https://www.googletagmanager.comhttps://*.cookiebot.comhttps://*.google-analytics.com
That’s normal in real companies. What’s not normal is assuming those broad allowances somehow solve editor-related CSP issues.
They don’t.
If CodeMirror 6 is self-hosted, those third-party script sources are irrelevant to the editor. Keep your debugging disciplined:
script-srcerrors: check script loading and inline executionstyle-srcerrors: check stylesheets, style blocks, style attributesconnect-srcerrors: check fetch/XHR/WebSocket/EventSourceimg-srcerrors: check data URLs, icons, preview imagesfont-srcerrors: check custom fonts used by your UI
Read the exact violation and map it to the directive. CSP is much easier when you stop treating it like magic.
Mistake #8: Shipping without Report-Only first
If you’re tightening CSP around an existing editor page, don’t jump straight to enforcement unless the page is tiny and fully understood.
Use Content-Security-Policy-Report-Only first:
Content-Security-Policy-Report-Only:
default-src 'self';
script-src 'self';
style-src 'self';
connect-src 'self';
img-src 'self' data:;
font-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none';
This lets you see what would break before users see it break.
For editor-heavy pages, that matters. The visible editor might load fine while a background save call, worker, or telemetry request quietly fails.
A sane baseline for CodeMirror 6
If I were starting fresh with a self-hosted CodeMirror 6 integration, I’d begin here:
Content-Security-Policy:
default-src 'self';
script-src 'self';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none';
Then I’d add only what the app proves it needs.
If you use nonced inline bootstrapping:
Content-Security-Policy:
default-src 'self';
script-src 'self' 'nonce-abc123';
style-src 'self';
img-src 'self' data:;
font-src 'self';
connect-src 'self';
base-uri 'self';
form-action 'self';
frame-ancestors 'none';
object-src 'none';
And if your app really needs extra endpoints:
connect-src 'self' https://api.example.com wss://collab.example.com;
That’s the pattern: start tight, verify with actual violations, and resist the urge to throw unsafe-inline at every problem.
CodeMirror 6 usually isn’t the thing forcing a weak CSP. Most of the time, it’s everything around it. Debug the page honestly, and you can keep both the editor and the policy in good shape.