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-src to allow your stylesheet
  • font-src if you use icon fonts or custom fonts
  • img-src for images, SVGs, and maybe data: URLs
  • default-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 overridden
  • style-src 'self' allows Spectre.css from your server
  • img-src 'self' data: covers local images and inline base64 icons/images
  • font-src 'self' is enough if you host fonts yourself
  • object-src 'none' kills old plugin attack surface
  • frame-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-src uses a nonce and 'strict-dynamic'
  • style-src still needs 'unsafe-inline', likely because of third-party injected styles
  • connect-src is where analytics and telemetry usually expand
  • frame-src is 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-src
  • style-src
  • connect-src
  • frame-src
  • img-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.