A Content-Security-Policy (CSP) is an HTTP response header that tells the browser which origins may supply scripts, styles, images, frames and connections for a page. The safe way to write one is to send it as Content-Security-Policy-Report-Only first, read the violation reports, tighten the source lists, and only then switch to the enforcing Content-Security-Policy header. The FindUtils CSP Header Generator builds the header line, the matching <meta> tag and a list of warnings from presets and per-directive source lists, in your browser.
This guide walks through that path, explains what the common directives control, and covers the two places people most often get CSP wrong: 'unsafe-inline' in script-src, and delivering the policy in a <meta> tag.
What Does a Content-Security-Policy Header Look Like?
A CSP header is a list of directives, separated by semicolons, each followed by the sources it allows. This is the generator's Strict preset:
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; object-src 'none'; frame-ancestors 'none'; base-uri 'none'; form-action 'self'; upgrade-insecure-requests
Keywords such as 'self' and 'none' must be in single quotes; hosts and schemes (https://cdn.example.com, data:) must not. A missing quote is not an error the browser reports. The browser reads self as a host name and quietly matches nothing. The generator quotes bare keywords for you and refuses a source that is not valid CSP syntax, such as https//cdn.example.com or a host with a space in it.
Step 1: Start in Report-Only Mode
Content-Security-Policy-Report-Only is a header that makes the browser check every resource against the policy and report violations without blocking anything. MDN describes it as the way to experiment with a policy by monitoring its effects rather than enforcing it. An enforced policy that misses one CDN host breaks that part of the site for every visitor; a Report-Only policy breaks nothing.
In the generator, pick a preset, switch on Report-Only mode, and add a report-uri if you collect reports:
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self'; object-src 'none'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'; report-uri /csp-report
Two things change in Report-Only mode. The generator leaves upgrade-insecure-requests out of the header, and it produces no <meta> tag, because the CSP specification does not support the Report-Only header inside a <meta> element.
On report-uri: CSP Level 3 deprecates it in favour of report-to, which names an endpoint declared in a separate Reporting-Endpoints header. The generator writes report-uri. If you also want report-to, add it and its endpoint header yourself.
Step 2: Read the Reports
Each violation is sent as a JSON POST to your report-uri. A report for a script from a host you forgot to list looks like this (fields per MDN's description of the report-uri format; values are illustrative):
{
"csp-report": {
"document-uri": "https://example.com/pricing/",
"violated-directive": "script-src-elem",
"effective-directive": "script-src-elem",
"blocked-uri": "https://cdn.example.net/widget.js",
"disposition": "report"
}
}Read blocked-uri and effective-directive together: this one says the page loads a script from cdn.example.net and script-src does not allow it. Each report forces a decision. Either the resource is legitimate and its host goes into the directive, or it is not and you remove it from the page. Browser developer tools show the same violations in the console while you browse, which is quicker for a first pass on staging.
Let the Report-Only header run for long enough to cover pages that are rarely visited: checkout, account settings, error pages, anything behind a login.
Step 3: Tighten, Then Enforce
Add the hosts the reports justified, and nothing broader. https://cdn.example.net is better than https:, which allows every host on the web over HTTPS. When the reports stop showing legitimate resources, turn Report-Only off in the generator and deploy the same policy under Content-Security-Policy.
Keep report-uri on the enforcing header too. After enforcement, a report means something was blocked for a real visitor.
What Does Each Common Directive Control?
The directives below are the ones the generator shows by default. Descriptions follow MDN's CSP reference.
| Directive | Controls |
|---|---|
default-src | Fallback for any fetch directive you do not set (scripts, styles, images, fonts, connections, frames, media, objects, workers) |
script-src | JavaScript sources, including inline scripts and event handlers |
style-src | Stylesheet sources, including inline <style> and style attributes |
img-src | Images and favicons |
font-src | Fonts loaded with @font-face |
connect-src | fetch(), XHR, WebSocket and EventSource connections |
frame-src | Pages this page may load in <iframe> |
object-src | <object> and <embed> content |
frame-ancestors | Which sites may embed this page in a frame (clickjacking) |
base-uri | URLs allowed in a <base> element |
form-action | Where forms may submit |
default-src only covers fetch directives. frame-ancestors, base-uri and form-action do not fall back to it, so a policy with only default-src 'self' leaves all three unrestricted. The generator warns when base-uri or frame-ancestors is missing, and when object-src is not 'none'.
Why 'unsafe-inline' in script-src Defeats Most of the Point
The main job of a CSP is to stop injected script. A cross-site scripting bug usually injects inline code: a <script> block or an onerror= attribute written into your HTML. 'unsafe-inline' in script-src tells the browser to run every inline script on the page, and it cannot tell yours from the attacker's. The policy still restricts external script hosts, but the most common injection path stays open. The generator warns about it for that reason.
CSP offers two ways to allow your own inline scripts without allowing everyone's:
- Nonces. The server generates a fresh random value for every response, puts it in the header as
'nonce-...', and adds the same value to each legitimate<script nonce="...">. The CSP Level 3 specification says the nonce should be at least 128 bits and come from a cryptographically secure random number generator. An attacker who injects markup cannot guess the value. - Hashes.
'sha256-...'(orsha384,sha512) is the base64 digest of the exact contents of one inline script. It suits static pages, because any change to the script, even whitespace, changes the hash.
Content-Security-Policy: script-src 'self' 'nonce-4AEemGb0xJptoIGFP3Nd' 'unsafe-inline'; object-src 'none'; base-uri 'none'
Under CSP Level 2 and later, a browser ignores 'unsafe-inline' when the same directive also contains a nonce or a hash. That is why the header above is safe in current browsers and still works in very old ones, and why the generator does not warn about 'unsafe-inline' when a nonce or hash sits beside it. A static generator cannot issue a per-response nonce. That has to happen in your server or framework on every request. The value above is only a placeholder to show the syntax.
'unsafe-inline' in style-src is a smaller risk, and the Typical preset allows it because many sites and libraries set inline styles.
Header or meta Tag?
Send the HTTP header whenever you can set response headers. The <meta http-equiv="Content-Security-Policy"> tag is a fallback for hosts that do not let you, and it is weaker in ways the CSP specification spells out:
| HTTP header | <meta> tag | |
|---|---|---|
frame-ancestors | Works | Ignored |
report-uri | Works | Ignored |
sandbox | Works | Ignored |
| Report-Only | Content-Security-Policy-Report-Only | Not supported |
| Covers | The whole response | Only content after the tag is parsed |
The generator writes both. It leaves frame-ancestors and report-uri out of the <meta> tag and says which ones it dropped. It does not build a sandbox directive at all. If clickjacking protection matters, and for most pages it does, the header is the only option.
What the Three Presets Allow
- Strict allows scripts, styles, images, fonts and connections only from your own origin, sets
default-src 'none', and blocks framing withframe-ancestors 'none'. It produces no warnings, and it will break any page that loads a third-party script, font or inline style. - Typical site starts from
default-src 'self'and allows inline styles, images fromdata:or anyhttps:host, fonts fromdata:URLs, and framing by your own origin only. - With Google Analytics is Typical plus Google's documented host patterns. It adds
https://*.googletagmanager.comtoscript-src,https://*.google-analytics.comandhttps://*.googletagmanager.comtoimg-src, and those two plushttps://*.analytics.google.comtoconnect-src. These are the hosts Google lists in its CSP guidance for Google Analytics 4 and Tag Manager.
The Google Analytics preset does not allow inline scripts. The standard gtag.js install includes an inline <script> that configures dataLayer, and this policy blocks it until you add a nonce or hash for it (Google's guidance recommends a nonce). Some Google features, such as Google Signals, need additional hosts that Google's guidance lists separately. Check your reports before you enforce.
Check the Live Header After You Deploy
The generator checks the policy text you build. It does not fetch your site. After deploying, paste the real response headers into the Security Headers Analyzer to confirm the header your server actually sends, alongside HSTS and the other security headers. The security headers guide covers how to capture them with curl -I.
The same generator is available through the FindUtils REST API (csp-generate) and as the csp_generate MCP tool. Those calls send your directives to FindUtils over TLS to build the policy.
Related Tools
- Cookie Analyzer checks the
Secure,HttpOnlyandSameSiteflags on cookies from the same response.HttpOnlylimits what an injected script can read. - URL Safety Checker checks links before you publish them or add their hosts to a policy.
- Data Sanitizer cleans and encodes untrusted input, the escaping layer a CSP backs up rather than replaces.
- Meta Tag Generator writes the other
<head>tags a page needs.
FAQ
Does a CSP replace escaping output? No. A CSP limits what an injected script can do and where it can load from. Escaping and sanitizing output is what stops the injection in the first place. Use both.
Can I combine several CSP headers? Yes. When a response carries more than one policy, the browser enforces each of them, so a resource must pass every policy to load. A second header can only make the result stricter.
Why was my source rejected?
The generator accepts keywords, nonces, hashes, schemes such as https: or data:, and hosts with an optional scheme, a leading *. wildcard, a port and a path. Quotes, commas, semicolons and angle brackets inside a source are refused, so they never reach the header.