Featured image of post Designing Modern Content Security Policies for Hugo blogs Featured image of post Designing Modern Content Security Policies for Hugo blogs

Designing Modern Content Security Policies for Hugo blogs

Constructing compliant, secure HTTP headers balancing analytics, advertisement tags, and asset safety.

Why CSP Matters for Static Sites

Static sites built with Hugo are inherently more secure than dynamic applications, but they still execute third-party scripts for analytics, ads, and embeds. A Content Security Policy (CSP) protects your visitors from XSS, data injection, and malicious script execution by controlling which resources can load.

Setting CSP via Cloudflare Pages

Cloudflare Pages supports custom _headers files for controlling HTTP response headers:

# static/_headers
/*
  Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'

For Hugo, place the _headers file in your static/ directory so it gets copied to the output:

static/
├── _headers
├── css/
└── js/

Building a Realistic CSP for Hugo

A blog that uses Google AdSense, Google Analytics, and embedded YouTube videos needs a more permissive policy. Here’s a production-ready CSP:

# static/_headers
/*
  Content-Security-Policy:
    default-src 'self';
    script-src 'self'
      'strict-dynamic'
      'sha256-...'   (inline script hash)
      https://www.googletagmanager.com
      https://pagead2.googlesyndication.com
      https://www.youtube.com;
    style-src 'self'
      'sha256-...'   (inline style hash)
      https://fonts.googleapis.com;
    img-src 'self'
      https://*.googleapis.com
      https://*.googlesyndication.com
      https://www.google.com
      https://*.doubleclick.net
      https://*.youtube.com
      data:;
    font-src 'self'
      https://fonts.gstatic.com;
    frame-src 'self'
      https://www.youtube.com
      https://www.google.com;
    connect-src 'self'
      https://*.google-analytics.com;
    report-uri /csp-report;

Using Nonces vs Hashes

For inline scripts (common in Hugo themes), you have two options:

Nonces (unique per-request):

<script nonce="ABC123">console.log("safe")</script>

CSP: script-src 'nonce-ABC123'

Nonces work best for dynamic sites but require server-side generation, which is difficult for fully static Hugo sites.

Hashes (content-based):

<script>console.log("safe")</script>

CSP: script-src 'sha256-...'

Hashes are ideal for static sites because they remain constant across deploys. Calculate the hash of your inline scripts and add them to the policy:

echo -n 'console.log("safe")' | openssl dgst -sha256 -binary | base64

Understanding strict-dynamic

The 'strict-dynamic' directive is a powerful CSP feature. It tells the browser to trust scripts loaded by an already-trusted script, reducing the need to enumerate all CDN URLs:

script-src 'self' 'strict-dynamic' 'sha256-ABC...';

With strict-dynamic, if your main bundle (trusted via hash) loads a CDN script, that CDN script is automatically trusted. This significantly simplifies CSP management for sites with many third-party scripts.

CSP for Google AdSense

AdSense requires specific CSP allowances. Key directives:

DirectiveRequired Sources
script-srchttps://pagead2.googlesyndication.com, 'unsafe-inline' (or hash)
img-srchttps://*.googlesyndication.com, https://*.doubleclick.net
frame-srchttps://googleads.g.doubleclick.net

Consider using 'unsafe-inline' only as a last resort — hashes are preferable.

CSP Reporting

Configure a report-uri or report-to directive to receive violation reports:

Content-Security-Policy: ...; report-uri /csp-report;

You can use a service like report-uri.com or Cloudflare CSP Analytics to collect and visualize reports. This helps identify blocked resources without breaking functionality.

Testing Your CSP

Before deploying, test with a restrictive policy using Content-Security-Policy-Report-Only:

Content-Security-Policy-Report-Only: default-src 'self'; report-uri /csp-report;

This header enforces nothing but sends violation reports, letting you identify issues safely.

Conclusion

A well-configured CSP protects Hugo blog visitors from XSS and script injection while allowing legitimate third-party services to function. By combining strict-dynamic, script hashes, and incremental deployment via report-only mode, you can build a robust security posture that balances protection with functionality.