← All guides
High Reported as: Content-Security-Policy present

Missing Content-Security-Policy

What a Content-Security-Policy header does, why SecScan reports its absence as High, and how to ship one without breaking your app.

A Content-Security-Policy (CSP) tells the browser which sources it is allowed to load code from. Without one, any HTML injection anywhere in your application becomes script execution: the browser has no reason to refuse a <script> tag just because it was not supposed to be there.

This is why the check is graded High rather than Medium. A missing CSP is not itself a vulnerability — it is the absence of the control that would have contained one.

What SecScan checks

Three separate findings come out of one header:

CheckSeverityMeaning
Content-Security-Policy presentHighNo policy at all
Policy contains unsafe-inlineMediumInline <script> still executes
Policy contains unsafe-evalMediumeval() and new Function() still execute

The two unsafe-* findings matter because a policy carrying both blocks very little of what an injected payload actually does. Shipping script-src 'self' 'unsafe-inline' and calling it done is the most common way to pass a header checklist while leaving the hole open.

How to fix it

Start in report-only mode. Content-Security-Policy-Report-Only sends violation reports without blocking anything, so you can see what a policy would break before it breaks it for real users:

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  frame-ancestors 'none';
  base-uri 'self';
  report-uri /csp-report

Collect reports for a week of real traffic. Every violation is either something you need to allowlist or something you should not have been loading. When the report volume settles, rename the header to Content-Security-Policy.

Getting rid of unsafe-inline

Inline scripts are the usual reason a policy stalls at unsafe-inline. Two ways out:

  • Nonces. Generate a random value per response, put it on the header (script-src 'nonce-r4nd0m') and on each legitimate inline tag (<script nonce="r4nd0m">). The nonce must be unpredictable and must change every response — a static nonce is the same as no nonce.
  • Hashes. For inline blocks that never change, script-src 'sha256-…' pins the exact content. No per-request work, but every edit to the block changes the hash.

Note that unsafe-inline is ignored by browsers when a nonce or hash is also present, so you can leave it in place as a fallback for very old clients without weakening modern ones.

frame-ancestors replaces X-Frame-Options

If you set frame-ancestors, it supersedes X-Frame-Options in every browser that supports CSP. SecScan reports both because the older header is still what some scanners and compliance checklists look for, and costs nothing to send.

Verify after deploying. A CSP set by your application can be silently overwritten by a CDN or reverse proxy in front of it. Re-run the scan against the public URL, not localhost.

Last reviewed 2026-09-07