โ† Blog  ยท  October 2026

Content Security Policy Monitoring: Detect CSP Changes Automatically

A Content Security Policy that silently changes after a deployment can break your site, allow cross-site scripting, or block legitimate scripts without anyone knowing until users start complaining.

Content Security Policy is one of the most powerful security headers available โ€” and one of the most fragile. A CSP that protects your users can change or disappear in seconds when you update a CDN rule, rotate a nonce, or modify a reverse proxy config. The effects range from subtle (a third-party widget stops loading) to severe (a broken site that blocks all JavaScript).

This guide covers how CSP regressions happen, how to check your current policy, and how to monitor it automatically so you find out before your users do.

๐Ÿ” Check your CSP right now: Free security headers checker โ€” see your Content-Security-Policy and all other headers โ†’

What Content Security Policy does

The Content-Security-Policy header tells browsers which sources are allowed to load scripts, styles, images, fonts, and other resources on your page. A strict CSP blocks cross-site scripting (XSS) attacks by refusing to execute inline scripts and restricting external script sources to an explicit allowlist.

A minimal script-blocking CSP looks like this:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'

This policy says: load everything from the same origin by default, allow scripts only from the current origin and cdn.example.com, and allow inline styles (common requirement for frameworks that inject inline CSS).

When this header disappears or gets weakened โ€” for example, by someone adding script-src 'unsafe-inline' * to fix a broken widget โ€” your XSS protection is gone. Browsers won't warn you. Users won't notice unless they're actively exploited.


How CSP headers regress

CDN config changes

Cloudflare Transform Rules, AWS CloudFront response headers policies, and Fastly VCL configs all sit in front of your application and can override or strip the CSP your app sends. When a team member updates a CDN rule without realizing it affects security headers, the CSP can change without any application deployment happening.

Nonce rotation without a monitoring update

CSPs that use nonces (script-src 'nonce-abc123') require generating a new nonce on every request. If a deployment changes the nonce generation logic, changes the CSP header string, or accidentally hard-codes a nonce, the policy becomes either broken or trivially bypassable.

Framework and middleware upgrades

Many frameworks set security headers automatically โ€” Helmet.js, Django's SecurityMiddleware, Rails' secure_headers gem. When you upgrade these packages, the default CSP values often change to reflect new browser support. What was a strict policy on version 4 may become looser on version 6, or stricter in ways that break your legitimate asset loads.

Proxy and load balancer changes

Moving traffic between nginx and a load balancer, adding a new reverse proxy, or changing SSL termination layers can silently drop headers that were configured at a layer that's no longer in the request path. The header was there last Tuesday; it's gone today because the traffic routing changed.

The silent regression problem: Unlike a broken API or a 500 error, a missing or weakened CSP produces no application logs, no error pages, and no monitoring alerts unless you specifically watch for it. It can stay undetected for weeks.

How to check your current CSP

curl from the command line

curl -s -I https://yourdomain.com | grep -i "content-security-policy"

If this produces no output, your site has no CSP set. If the output looks different from what you expect, something has changed since you last deployed.

For a full headers check across all six major security headers at once:

curl -s -I https://yourdomain.com | grep -iE "content-security|x-frame|x-content-type|strict-transport|referrer-policy|permissions-policy"

Browser DevTools

Open Chrome DevTools โ†’ Network tab โ†’ click any document request โ†’ scroll to Response Headers. You'll see the CSP value sent by the server (not necessarily what the meta tag in HTML says โ€” the HTTP header takes precedence for most enforcement).

Free security headers checker

The security headers checker shows a full breakdown of your CSP and all other security headers in one check. It flags missing directives, grades your overall security posture, and explains what each header does.


How to monitor CSP automatically

Checking manually is useful for a specific deployment. Monitoring is different: you want to know the moment the header changes on its own โ€” whether or not a deployment happened.

Method 1: cron script with curl + diff

Save the known-good CSP value to a file, then run a cron job that compares the live header to the saved value:

#!/bin/bash
# /usr/local/bin/check-csp.sh
DOMAIN="yourdomain.com"
EXPECTED_CSP="default-src 'self'; script-src 'self' https://cdn.yourdomain.com"
EMAIL="security@yourdomain.com"

CURRENT_CSP=$(curl -s -I "https://$DOMAIN" | grep -i "content-security-policy" | sed 's/content-security-policy: //i' | tr -d '\r')

if [ "$CURRENT_CSP" != "$EXPECTED_CSP" ]; then
  echo "CSP changed on $DOMAIN.
Expected: $EXPECTED_CSP
Got: $CURRENT_CSP" | mail -s "Security alert: CSP changed on $DOMAIN" "$EMAIL"
fi
# crontab: check every 6 hours
0 */6 * * * /usr/local/bin/check-csp.sh

This approach works but requires maintaining the expected value as your CSP legitimately evolves, and it only alerts on exact string differences โ€” a whitespace change or header case difference triggers a false positive.

Method 2: GitHub Actions scheduled check

name: CSP Monitor
on:
  schedule:
    - cron: '0 8 * * *'  # Daily at 8am UTC

jobs:
  check-csp:
    runs-on: ubuntu-latest
    steps:
      - name: Check Content Security Policy
        run: |
          CSP=$(curl -s -I https://yourdomain.com | grep -i "content-security-policy")
          if [ -z "$CSP" ]; then
            echo "::error::Content-Security-Policy header is missing!"
            exit 1
          fi
          echo "CSP present: $CSP"
          # Optionally: check for dangerous values
          if echo "$CSP" | grep -q "unsafe-eval"; then
            echo "::warning::CSP contains unsafe-eval"
          fi

GitHub will email you when the action fails. This catches a missing CSP but won't alert on a weakened-but-present policy unless you add more specific checks.

Method 3: Dedicated security header monitoring

The most reliable approach is a service that monitors your security headers continuously from external infrastructure and alerts you the moment any header changes โ€” including partial changes, weakened values, and header removals.

๐Ÿ›ก๏ธ Monitor your CSP and all security headers automatically

HeaderGuard checks your HTTP security headers daily and sends an email alert the moment anything changes โ€” Content-Security-Policy, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, and Permissions-Policy.

Add your domain once. Get notified before a security regression turns into a breach report.

Join the HeaderGuard waitlist โ†’ Check your headers free

CSP directives quick reference

DirectiveControlsCommon safe value
default-srcFallback for all resource types'self'
script-srcJavaScript sources'self' + explicit CDN origins
style-srcCSS sources'self' 'unsafe-inline' (often needed)
img-srcImage sources'self' data: https:
connect-srcXHR/fetch/WebSocket targets'self' + your API origins
frame-ancestorsWho can embed your page'none' or 'self'
upgrade-insecure-requestsForce HTTPS on mixed contentInclude if serving HTTPS

Values to avoid without understanding them:

Check your current CSP values and grade with the free security headers checker โ†’