Directives in, a deployable CSP header out - with the usual self-defeating mistakes flagged.
- Written against
- CSP Level 3 (W3C Working Draft) and CSP Level 2 (W3C Recommendation, 2016)
- Behaviour verified in
- Chrome, Firefox, Safari and Edge
- Last reviewed
- August 2026
- Where it runs
- Your browser tab. No policy is uploaded.
What this generator is for
Build a Content-Security-Policy directive by directive and output it as an HTTP header, an Apache or nginx line, a PHP call or a meta tag. The notes call out the settings that quietly defeat the policy - unsafe-inline, a bare wildcard, a missing default-src - and the directives a meta tag cannot express at all. Start in report-only mode; the tool explains why.
Deploy it in report-only mode first
Content-Security-Policy-Report-Only sends the browser the same policy and asks it to report violations instead of enforcing them. Nothing breaks, and your report endpoint fills up with every inline script, third-party widget and analytics beacon you had forgotten about. Read that list for a week, fix the policy, then switch to the enforcing header.
You can ship both headers at once - report-only for the policy you are working towards, enforcing for the one you already trust. That is how you tighten a policy on a live site without a maintenance window.
unsafe-inline is the whole ballgame
A CSP exists to stop injected script from executing. 'unsafe-inline' in script-src allows every inline <script> block and every onclick attribute - which is precisely the mechanism XSS uses. A policy with it is documentation, not a defense.
The way out is a nonce: put a fresh random value in the header on every response (script-src 'nonce-abc123') and the same value on each script tag you trust. It has to be per-response and unpredictable, or an attacker just reads the nonce out of the page and reuses it. Styles are the harder half in practice, since a lot of libraries write inline style attributes.
Four directives that cost nothing and close the most doors
object-src 'none' and base-uri 'none' are almost free and both stop real attacks. Legacy plugin content is the reason object-src exists and nothing on a modern site needs it. base-uri matters for a subtler reason: an injected <base> tag rewrites every relative URL on the page, so an attacker who can inject one tag can repoint all of your relative script sources at their own host without ever touching script-src.
The other two are about where the page can go rather than what it can load. frame-ancestors 'none' is clickjacking protection and replaces X-Frame-Options; form-action 'self' stops an injected form from posting your visitor's password somewhere else. Neither inherits from default-src, so a policy without them is unrestricted on both counts however strict the rest of it looks. That is the most common gap in policies that otherwise look complete.
What a CSP does not do
It is a second line of defence, not the first. A CSP sanitises nothing: the fix for cross-site scripting is still contextual output encoding, and the policy is what limits the damage on the day that fails. Selling it internally as "we have a CSP, so XSS is handled" is how a real hole survives a review.
It also does not stop everything a script can do once it runs. connect-src restricts fetch and WebSocket, but a determined payload can still move data out through a navigation or a resource load the policy does allow, and the directive meant to cover that case - navigate-to - was never shipped by browsers. Nothing in a CSP touches browser extensions either; they inject script on the visitor's side and will fill your report endpoint proving it.
How a policy rots
Policies rarely fail on the day they ship. They fail three months later, when someone adds a tag manager and the fastest way to make it work is to append a host - or, when that does not work, 'unsafe-inline'. The end state is a header long enough that nobody reads it and permissive enough that it blocks nothing.
Two habits prevent most of that. Keep the policy in version control next to the code rather than in a hosting control panel, so every change is a reviewable diff with a reason attached. And keep the report endpoint alive after rollout: a spike in violations for a directive nobody touched is the earliest signal you get that a third-party script has started doing something new.
The policy is assembled in this tab. The domains you allow amount to a list of every third party your site talks to, and that list stays with you.