Updated:

Content Security Policy

Content Security Policy, or CSP, is an HTTP header that tells the browser where a page may load scripts, styles, images, fonts, and runtime network requests from.

For a static site, CSP is especially useful: if the site should not call third-party origins at runtime, the browser can enforce that.

Baseline policy for a static site

Content-Security-Policy: default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; connect-src 'none'; img-src 'self' data:; script-src 'self'; style-src 'self'; font-src 'self'

This is a strict and understandable profile for a site with:

  • no external fonts;
  • no CDN;
  • no analytics;
  • no forms;
  • no runtime fetch calls;
  • local CSS/JS only.

What each directive means

DirectiveMeaning
default-src 'self'Current origin is allowed by default
base-uri 'self'Restricts the <base> element
form-action 'none'Blocks form submission
frame-ancestors 'none'Blocks embedding the page in a frame
object-src 'none'Blocks legacy object/embed content
connect-src 'none'Blocks runtime network requests
img-src 'self' data:Allows local images and data URI images
script-src 'self'Allows local scripts only
style-src 'self'Allows local styles only
font-src 'self'Allows local fonts only

Why connect-src 'none'

If the site should not make runtime network requests, explicitly block them.

This blocks:

  • fetch;
  • XMLHttpRequest;
  • WebSocket;
  • EventSource;
  • some beacon requests.

For local browser tools, this is fine when all logic runs in the browser without sending data anywhere.

Why no inline scripts

A strict CSP without 'unsafe-inline' blocks inline JavaScript and inline CSS.

Keep scripts and styles in local files:

/assets/*.js
/assets/*.css

This makes behavior easier to check, cache, and reason about.

Nginx example

add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; connect-src 'none'; img-src 'self' data:; script-src 'self'; style-src 'self'; font-src 'self'" always;

Use always, so the header is present on 404 and other non-standard responses too.

Check with curl

curl -kI https://getsrv.app/ | grep -i content-security-policy

Expected:

content-security-policy: default-src 'self'; ...

Check all main security headers:

curl -kI https://getsrv.app/ \
  | grep -Ei 'content-security-policy|x-frame-options|x-content-type-options|referrer-policy|permissions-policy'

Check in the browser

Open DevTools → Console.

Healthy result:

  • no CSP errors;
  • no CSS/JS loading errors;
  • no requests to third-party origins.

If CSP blocks something, the browser usually prints the directive that caused it.

Common mistakes

Mistake 1. Adding an external font

Example:

<link href="https://fonts.example.com/font.css" rel="stylesheet">

The current CSP blocks it. For an autonomous static site, avoid external fonts.

Mistake 2. Adding inline script

Example:

<script>
  console.log('hello')
</script>

With script-src 'self', this script is blocked. Move it to a local file.

Mistake 3. A tool starts using fetch

If a tool starts calling an API, connect-src 'none' blocks it. For this site, the better rule is: tools run locally and do not send data anywhere.

Mistake 4. CSP exists on 200 but not on 404

Make sure Nginx uses always:

add_header Content-Security-Policy "..." always;

When to expand the policy

Expand CSP only for a specific requirement.

Examples:

  • local images from /assets/ — already allowed by img-src 'self';
  • external API — add a specific origin to connect-src;
  • external font — add a specific origin to font-src and style-src.

Avoid:

script-src *
style-src *
connect-src *

That makes CSP much less useful.

Minimal check after changing CSP

curl -kI https://getsrv.app/ | grep -i content-security-policy
curl -kI https://getsrv.app/no-such-page | grep -i content-security-policy

Then open the site in a browser and check Console.

If Console is clean, CSS/JS load correctly, and Network does not show third-party runtime requests, the policy behaves as intended.