Spectre.css is easy to secure with CSP because it’s just CSS. No JavaScript runtime, no weird asset loader, no inline script requirements. That’s the good part.
The catch is everything around it: icon fonts, third-party CDNs, analytics, consent banners, inline styles from old templates, and framework glue code you forgot was there.
This guide is the practical version. Copy-paste policies first, then adjust for your setup.
What Spectre.css needs from CSP
By itself, Spectre.css usually needs:
style-srcto allow your stylesheetfont-srcif you use icon fonts or custom fontsimg-srcfor images, SVGs, and maybedata:URLsdefault-src 'self'as a sane baseline
If you self-host Spectre.css and your site is plain HTML with no inline JS, your CSP can be pretty tight.
Minimal CSP for a self-hosted Spectre.css site
If you downloaded Spectre.css and serve it from your own domain:
Content-Security-Policy: default-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; script-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'
That works well for:
- static marketing pages
- docs sites
- small apps with self-hosted CSS/JS
- no third-party embeds
Why this works
default-src 'self'blocks everything except your own origin unless overriddenstyle-src 'self'allows Spectre.css from your serverimg-src 'self' data:covers local images and inline base64 icons/imagesfont-src 'self'is enough if you host fonts yourselfobject-src 'none'kills old plugin attack surfaceframe-ancestors 'none'prevents clickjacking unless you actually need embedding
If you need your site embeddable in another app, change frame-ancestors.
CSP for Spectre.css from a CDN
If you load Spectre.css from a CDN, allow that host in style-src.
Example with a generic CDN host:
Content-Security-Policy: default-src 'self'; style-src 'self' https://cdn.example.com; img-src 'self' data:; font-src 'self' https://cdn.example.com; script-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'
Replace https://cdn.example.com with the actual CDN you use.
I usually prefer self-hosting Spectre.css. Fewer CSP exceptions, fewer third-party dependencies, fewer surprises when a CDN changes caching or headers.
If your HTML still uses inline styles
A lot of older Spectre.css examples use inline style="" attributes for spacing or layout hacks. CSP will block those unless you allow inline styles.
You can do this:
Content-Security-Policy: default-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; script-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'
But I’d treat 'unsafe-inline' in style-src as technical debt, not a clean solution.
Better: move inline styles into classes.
Bad
<div class="container" style="margin-top: 2rem;">
<h1>Hello</h1>
</div>
Better
<div class="container mt-2">
<h1>Hello</h1>
</div>
.mt-2 { margin-top: 2rem; }
Then your CSP stays strict:
Content-Security-Policy: default-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; script-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'
If Spectre.css is bundled into your app
If you import Spectre.css into your build pipeline and output a local CSS bundle, CSP gets simpler.
For example, in a frontend build:
import 'spectre.css/dist/spectre.min.css';
import './app.css';
Then just allow your own styles:
Content-Security-Policy: default-src 'self'; style-src 'self'; script-src 'self'; img-src 'self' data:; font-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'
This is my preferred setup for production.
CSP for Spectre.css with Google Tag Manager and Cookiebot
This is where “simple CSS framework” stops being the interesting part. Spectre.css itself doesn’t force a messy CSP, but analytics and consent tooling often do.
Here’s the real-world header you provided, reformatted:
Content-Security-Policy:
default-src 'self' https://www.googletagmanager.com https://*.cookiebot.com https://*.google-analytics.com;
script-src 'self' 'nonce-YWVlY2Q3NGYtMTUzOC00N2ExLWE2ODYtMTdiYTA0YmYzZTVk' '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’s a solid pattern for a Spectre.css site with marketing tooling layered on top.
What matters here
script-srcuses a nonce and'strict-dynamic'style-srcstill needs'unsafe-inline', likely because of third-party injected stylesconnect-srcis where analytics and telemetry usually expandframe-srcis needed for consent UI if it renders in frames
If you use a nonce-based setup, generate a fresh nonce per response and apply it to allowed inline scripts.
Example HTML:
<script nonce="{{ .CSPNonce }}">
window.dataLayer = window.dataLayer || [];
</script>
<script
nonce="{{ .CSPNonce }}"
src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXXXX">
</script>
Example header:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic' https://www.googletagmanager.com; style-src 'self'; img-src 'self' data: https:; connect-src 'self' https://*.google-analytics.com https://*.googletagmanager.com; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'
If you want the deeper mechanics of nonces, hashes, and strict-dynamic, the official CSP docs are the place to start, and https://csp-guide.com is useful for directive-specific examples.
Nginx example
A straightforward Nginx config for a self-hosted Spectre.css site:
add_header Content-Security-Policy "default-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; script-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'" always;
If you use third-party style or script sources, add them explicitly.
Apache example
Header always set Content-Security-Policy "default-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; script-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'"
Express.js example
app.use((req, res, next) => {
res.setHeader(
'Content-Security-Policy',
"default-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; script-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'"
);
next();
});
Common Spectre.css CSP breakages
These are the ones I see most often.
1. Icons or fonts don’t load
You forgot font-src, or your fonts come from a CDN you didn’t allow.
font-src 'self' https://fonts.example.com
2. Background images in CSS are blocked
Your stylesheet references images on another host, but img-src only allows self.
img-src 'self' data: https://assets.example.com
3. Layout breaks after enabling CSP
Usually caused by inline styles being blocked.
Quick fix:
style-src 'self' 'unsafe-inline'
Real fix: remove inline styles.
4. Third-party widgets silently fail
They often need a mix of:
script-srcstyle-srcconnect-srcframe-srcimg-src
Don’t guess. Check the browser console CSP violations and add only what’s required.
Good starter policies
Tight policy for a plain Spectre.css site
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'; object-src 'none'; frame-ancestors 'none'
Spectre.css site with CDN-hosted CSS
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' https://cdn.example.com; img-src 'self' data:; font-src 'self' https://cdn.example.com; connect-src 'self'; base-uri 'self'; form-action 'self'; object-src 'none'; frame-ancestors 'none'
Spectre.css site with GTM and analytics
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{{NONCE}}' 'strict-dynamic' https://www.googletagmanager.com https://*.google-analytics.com; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self'; connect-src 'self' https://*.google-analytics.com https://*.googletagmanager.com; frame-ancestors 'none'; base-uri 'self'; form-action 'self'; object-src 'none'
My default recommendation
For Spectre.css, I’d start 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'; object-src 'none'; frame-ancestors 'none'
Then expand only when your app proves it needs more.
That’s the nice thing about Spectre.css. It doesn’t drag in a giant JavaScript surface area, so your CSP can stay readable. And readable CSPs are the ones people actually maintain.