The short answer

Send a Content-Security-Policy that only allows the scripts you intend to run. The most robust form uses a random nonce on each page:

Content-Security-Policy: script-src 'nonce-R4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'

Your server generates a new nonce for every response and adds nonce="R4nd0m" to each legitimate <script> tag. An injected script does not know the nonce, so it is blocked. Roll it out in report-only mode first, then enforce it.

What a policy stops

An injected script with and without a Content-Security-Policy Top, no policy: an attacker posts a comment containing a script tag, the site stores it and shows it to other visitors, and their browsers run it, sending session cookies to the attacker. Bottom, with a nonce-based policy: the same injected script reaches the browser, but it has no valid nonce, so the browser refuses to run it and can send a violation report to the site. No policy Attacker Posts a comment with <script>…</script> Site Stores and shows it Visitor’s browser Runs every script Script acts Steals the session Rewrites the page Any injected script runs with full access Nothing tells the site it happened Strict policy Attacker Posts the same comment Site Shows it, with a CSP header Browser checks CSP script-src 'nonce-…' Script blocked No valid nonce Only scripts the site marked can run A violation report can alert you An injected script with and without a Content-Security-Policy Top, no policy: an attacker posts a comment containing a script tag, the site stores it and shows it to other visitors, and their browsers run it, sending session cookies to the attacker. Bottom, with a nonce-based policy: the same injected script reaches the browser, but it has no valid nonce, so the browser refuses to run it and can send a violation report to the site. No policy Attacker Posts a comment with <script>…</script> Site Stores and shows it Visitor’s browser Runs every script Script acts Steals the session Rewrites the page Any injected script runs with full access Nothing tells the site it happened Strict policy Attacker Posts the same comment Site Shows it, with a CSP header Browser checks CSP script-src 'nonce-…' Script blocked No valid nonce Only scripts the site marked can run A violation report can alert you
CSP is a second line of defence. It does not remove the injection bug, but it stops the browser from running what was injected.

This kind of attack is called cross-site scripting (XSS), and it has been on the OWASP Top 10 list of web application risks for two decades. Escaping user input is the first defence; CSP limits the damage when something is missed.

How DNS Panopticon detects it

Security Posture reads the Content-Security-Policy header on your homepage and works out whether an injected script could still run. It looks at script-src-elem, script-src-attr, script-src and default-src, in the order browsers fall back through them. When several policies are sent, a script must pass all of them. The result appears on the Website Application Security card.

FindingSeverityWhen it appears
No Content-Security-PolicyMediumNo enforced policy. A Content-Security-Policy-Report-Only header alone is noted, since it reports but does not block.
Content-Security-Policy allows any scriptLowThe policy allows 'unsafe-inline' without a nonce, hash or 'strict-dynamic', or allows scripts from any site through a source such as *, https: or http:.
Content-Security-Policy is setPassThe policy limits which scripts may run.

A <meta http-equiv> policy in the page HTML is not read; send the policy as a header.

How to check it yourself

Command or toolWhat to look for
curl -sI https://example.comA content-security-policy line. Check script-src (or default-src if there is none) for 'unsafe-inline', * or https:.
https://csp-evaluator.withgoogle.com/Paste the policy into Google’s CSP Evaluator for a directive-by-directive review.

In the browser, open developer tools. The Console lists every resource the policy blocked, with the directive that blocked it, which is the quickest way to tune a policy.

Why a weak policy is nearly as bad as none

  • 'unsafe-inline' allows any inline <script> block or onclick handler, which is exactly what most injected code looks like. A nonce or hash disables it in browsers that support them.
  • * or https: allows scripts from any website, so an attacker can host their script anywhere and point to it.
  • Broad allowlists of big CDNs can be bypassed when those CDNs host old libraries or JSONP endpoints that run attacker-chosen code. Nonces with 'strict-dynamic' avoid that problem.

How to fix it

  1. Inventory your scripts. List first-party scripts, inline scripts and third-party tags (analytics, chat, tag managers).
  2. Start in report-only mode. Send the policy as Content-Security-Policy-Report-Only with a report-to or report-uri endpoint and watch what would have been blocked.
  3. Add nonces or hashes. Generate a fresh random nonce per response and put it on every legitimate script tag. For static sites, list SHA-256 hashes of inline scripts instead. Move inline event handlers such as onclick into script files.
  4. Enforce it. Switch the header name to Content-Security-Policy once reports are quiet.
  5. Add the useful extras: object-src 'none', base-uri 'none', frame-ancestors 'self' (see clickjacking) and upgrade-insecure-requests (see mixed content).
ApproachExample script-srcGood for
Nonce-based (strict)'nonce-{random}' 'strict-dynamic'Sites rendered by a server or framework that can add a nonce to each page
Hash-based (strict)'sha256-…' 'strict-dynamic'Static sites with a few fixed inline scripts
Allowlist'self' https://cdn.example.netSimple sites with no inline scripts; easier, but weaker

Most frameworks can add nonces for you: for example Next.js middleware, Django’s django-csp package, Helmet for Express, or NWebsec for ASP.NET. CMS platforms often have a plugin, though many start with an allowlist that still includes 'unsafe-inline'; tighten it once the site works.

FAQ

Does CSP replace fixing XSS bugs?

No. Escape and validate user input first. CSP is defence in depth: it stops many injected scripts from running when a bug slips through.

Why does the policy break my analytics or tag manager?

Those tools load scripts from their own domains and often inject inline code. With 'strict-dynamic', scripts loaded by a trusted, nonced script are allowed, which covers most tag managers. Check the vendor’s CSP guide for anything else it needs.

Is a report-only policy enough?

No. Report-only shows what would be blocked but blocks nothing, which is why DNS Panopticon still treats it as missing. Use it while you tune the policy, then enforce.

Should I use a meta tag instead of a header?

Use the header where you can. A meta tag cannot set frame-ancestors or reporting, and only applies after the browser reaches it in the HTML.

Sources and further reading