jekcms ships a real, production Content Security Policy — not a strict template, and not a marketing claim. Here's exactly what it blocks, what it deliberately still allows, and why.
jekcms's production CSP includes 'unsafe-inline' for both script-src and style-src. This is a deliberate, documented trade-off, not an oversight: the Custom Header Scripts setting lets site owners paste their own tracking/verification snippets directly into the page, which can't be nonce-signed without breaking that feature, and the bundled themes and core templates use inline <script>/<style> in places. Removing unsafe-inline would require rewriting both of those before it could ship — it's an open item, not a hidden gap.
Self-Hosting Fonts Removes Two Directives
Fonts loaded from Google Fonts require two directives: font-src fonts.gstatic.com and style-src fonts.googleapis.com. jekcms's bundled themes, including the default Trends theme, currently load Inter and other typefaces from the Google Fonts CDN rather than self-hosting them — which is why both domains are allowlisted in the CSP. If you switch to self-hosted fonts on your own installation, which we'd recommend for both privacy and one fewer external request, you can drop both domains from your policy and rely on 'self' alone.
Explicit img-src Enumeration
The img-src directive needs to include every external domain your content actually loads images from. A common mistake is setting img-src https: to allow any HTTPS image source — this defeats most of the CSP's value against image-based attacks. jekcms's own default policy takes this shortcut (img-src 'self' data: https:) for compatibility with arbitrary embedded content; if you don't need that flexibility, enumerate your actual image sources explicitly instead: img-src 'self' cdn.yoursite.com data:.
Always Start in Report-Only Mode When You Tighten Further
If you plan to narrow jekcms's default policy for your own site, start with Content-Security-Policy-Report-Only and a report-uri pointing at a collection endpoint before enforcing anything. Collect violations for at least a week — weekend traffic often exercises code paths weekday monitoring misses. jekcms itself does not currently ship a built-in violation-logging endpoint; if you want one, you'll need to point report-uri at a service you control.
jekcms's Actual Production CSP
This is the real header jekcms sends in production (from Security.php), covering the default theme set and the built-in Google Analytics/AdSense/GTM and Facebook Pixel integrations:
Content-Security-Policy:
default-src 'self';
base-uri 'self';
object-src 'none';
frame-ancestors 'self';
script-src 'self' 'unsafe-inline'
https://cdn.jsdelivr.net https://cdnjs.cloudflare.com
https://www.googletagmanager.com https://www.google-analytics.com
https://pagead2.googlesyndication.com https://adservice.google.com
https://www.googleadservices.com https://connect.facebook.net;
style-src 'self' 'unsafe-inline'
https://fonts.googleapis.com https://cdn.jsdelivr.net;
font-src 'self' https://fonts.gstatic.com;
img-src 'self' data: https:;
frame-src 'self' https://googleads.g.doubleclick.net
https://tpc.googlesyndication.com https://www.google.com
https://www.facebook.com;
connect-src 'self' https://www.google-analytics.com
https://analytics.google.com https://www.googletagmanager.com
https://pagead2.googlesyndication.com https://adservice.google.com
https://www.facebook.com;
Notice what's not there: 'unsafe-eval'. None of jekcms's own code or its bundled third-party scripts (Tiptap, Google Analytics/AdSense/GTM, Facebook Pixel) require it, so it was never added to the script-src allowlist.
Directive-by-Directive Explanation
default-src 'self'— baseline for any resource type not explicitly listed; restricts everything to the same originobject-src 'none'— blocks Flash, Java applets, and other plugin content; common XSS vectors with no modern usebase-uri 'self'— prevents attackers from injecting a base tag to redirect relative URLs to a malicious domainframe-ancestors 'self'— equivalent toX-Frame-Options: SAMEORIGINbut with broader browser support; prevents clickjacking (jekcms sends both headers)
Why the AdSense and Analytics Domains Are There
The googlesyndication.com, adservice.google.com, googleadservices.com, and doubleclick.net domains in the policy above exist because jekcms's built-in Google integration plugin injects AdSense, GA4, and GTM snippets when you configure them in settings — not because every jekcms site runs ads. If you never enable AdSense on your installation, you can safely remove those domains from a customised policy. Google's ad infrastructure rotates and adds subdomains over time, so if you tighten the policy yourself, expect to revisit it occasionally rather than treating any allowlist as permanent.
Common Mistakes When Customising the Policy
- Adding
unsafe-evalbecause a library useseval()— find an alternative library instead; jekcms's own stack doesn't need it - Whitelisting CDN domains broadly (e.g.,
*.cloudflare.com) — attackers can host scripts on the same CDN - Forgetting
connect-srcfor AJAX requests — breaks admin panel functionality silently - Testing only the public frontend — the admin panel loads a different script set (the editor bundle, media picker) and needs its own check
Where This Stands Today, Honestly
jekcms applies one global CSP in production via Security.php — there's no separate, more permissive policy for /admin/ routes, and no nonce-based script loading yet. The code has an explicit TODO marking nonce migration as a larger refactor: it would require updating every inline script across the admin panel and bundled themes before 'unsafe-inline' could be dropped from script-src. There's also no built-in CSP violation logging to an admin-visible table — if you want to monitor violations, point report-uri at your own collection endpoint. None of this is hidden; it's a documented, load-bearing trade-off between security headroom and not breaking the Custom Header Scripts feature or existing themes.