# Build an XSS-proof CSP, or validate the one you already ship

> Online Content-Security-Policy builder and validator: compose an XSS-safe CSP header by directive, or audit an existing policy. Fully client-side, free.

Free in-browser tool (no upload): https://www.mauriziofonte.it/tools/csp-generator-validator

Two tools in one. The **builder** composes a **Content-Security-Policy** header by picking the main directives (`default-src`, `script-src`, `frame-ancestors` and others) with canonical values such as `'self'`, `'none'`, nonces and `'strict-dynamic'`. The **validator** analyses a CSP you paste in and flags the weaknesses that defeat its protection: `'unsafe-inline'`, a `*` wildcard, `data:` in `script-src`, a missing `default-src` or `frame-ancestors`. Everything is **client-side**: no data ever leaves your browser.

## How to use the builder and validator

1. **Set the directives in the builder** - Each directive has a dropdown with canonical values. The initial setup is already a safe baseline: `default-src 'self'`, scripts and styles from your origin only, `frame-ancestors 'none'` against clickjacking. Tighten with `'none'` whatever you do not need, and loosen only where the application requires it.
2. **Add trusted hosts and options** - In the additional hosts field list your legitimate third-party domains (the script CDN, the API endpoint). Enable `upgrade-insecure-requests` if you still have http:// assets, and set a `report-uri` to collect violations. Report-only mode generates the test header that observes without blocking.
3. **Generate and copy the header** - The Generate button produces the full `Content-Security-Policy` header, ready to paste into your Apache, Nginx or application middleware config. Copy it in one click. If you picked a nonce value, replace `{NONCE}` with the random token your server generates on every request.
4. **Validate an existing policy** - Paste the CSP you are already serving (or the whole header copied from `curl -I`) into the validator. The report highlights, in real time, what weakens the protection, assigns a severity to each finding, and computes an indicative hardening score. Fix it in the builder and regenerate.

## Why CSP is the zero-cost defense against XSS

**What it actually does.** A Content-Security-Policy is an HTTP response header that tells the browser which origins it may load and run resources from: scripts, styles, images, fonts, frames, network connections. It is a declarative allowlist. With `script-src 'self'` the browser only runs JavaScript served by your own domain and rejects everything else, including the payload an attacker manages to inject through a cross-site scripting flaw. CSP does not fix the underlying injection bug, but it neutralises the exploitation step: the injected script simply never runs. It is defense in depth at zero performance cost, enabled with a single line of configuration.

**The 'unsafe-inline' trap.** CSP's security model rests on one distinction: code coming from a trusted source runs, inline code (inside `<script>` or in `onclick=` attributes) does not, because the browser cannot tell legitimate inline from injected inline. Adding `'unsafe-inline'` to `script-src` reopens the exact door CSP was meant to close: any injected payload becomes executable again and the XSS protection drops to nothing. The modern alternatives are three: a random per-request **nonce** tagged only on legitimate scripts, an SHA-256 **hash** of the expected inline content, or `'strict-dynamic'`, which propagates trust from nonce-tagged scripts to the ones they dynamically load, freeing you from maintaining a host allowlist.

**Builder and validator, one workflow.** The builder starts from a deliberately restrictive baseline because in CSP security is the default and openness is the exception you justify. Composing the policy by choosing values avoids the syntax mistakes (a stray semicolon, a misspelled directive) that quietly make a CSP ineffective. The validator closes the loop on what already exists: paste the policy you serve and immediately see whether a `*`, a `data:` in `script-src` or a missing `default-src` is leaving a hole. Neither tool touches the network.

**Report-only before enforce.** Applying a strict CSP to a live site without testing breaks legitimate resources. That is what `Content-Security-Policy-Report-Only` is for: the policy is evaluated and every violation is logged and sent to your `report-uri`, but nothing is blocked. You watch real traffic for days or weeks, refine the policy against the reports, and only when false positives reach zero do you switch to the enforcing header. Private by design: the builder and validator run entirely in the browser, the site you are protecting is never contacted by our servers, so you can work on staging environments and intranets that are not publicly reachable.

## Glossary

- **CSP (Content-Security-Policy)**: HTTP response header declaring to the browser which sources are allowed for each resource type. The primary defense against XSS and data injection. Standardised by the W3C as CSP Level 3.
- **Directive**: A single rule of the policy, in the form `directive-name sources` (e.g. `script-src 'self'`). Directives are separated by semicolons within the header.
- **Source list**: The list of sources following a directive. It can hold quoted keywords (`'self'`, `'none'`), schemes (`https:`, `data:`), hosts (`https://cdn.example.com`) and wildcards.
- **Nonce**: Number used once. A random, unpredictable token the server generates on every request, placed in the directive as `'nonce-...'` and as a `nonce` attribute on legitimate scripts only. It makes inline safe without a host allowlist.
- **Hash**: A cryptographic digest (SHA-256, SHA-384, SHA-512) of the exact contents of an inline script or style, declared as `'sha256-...'`. The browser runs the inline only if its hash matches. The static alternative to a nonce.
- **strict-dynamic**: A keyword that propagates trust: scripts loaded by an already-trusted script (via nonce or hash) are authorised in turn, ignoring hosts and wildcards in the policy. It simplifies CSPs that load modules dynamically.
- **frame-ancestors**: The directive that decides which origins may embed the page in a `<frame>` or `<iframe>`. With `'none'` it is the clickjacking defense, more expressive than X-Frame-Options.
- **report-uri / report-to**: Directives that tell the browser where to send JSON reports of CSP violations. `report-uri` is the legacy form still supported; `report-to` is the modern successor built on the Reporting API. Essential for testing in report-only.

## Frequently asked questions about CSP

### What does a Content-Security-Policy actually do?

It tells the browser which origins may load and run each type of resource (scripts, styles, images, frames, connections). Anything outside the allowlist is blocked. The main benefit is against XSS: even if an attacker manages to inject code into the page, a tight `script-src` means the browser will not execute it.

### Does my data stay in the browser?

Yes, entirely. Both the builder and the validator run in JavaScript inside your browser. The policy you compose or paste is never sent to any server, and the site you are protecting is never contacted by our infrastructure. You can use the tool offline and on staging or intranet environments that are not reachable from outside.

### What is the difference between report-only and enforce?

The `Content-Security-Policy` header enforces the policy: resources outside the allowlist really are blocked. The `Content-Security-Policy-Report-Only` header evaluates the same policy but blocks nothing: it only logs violations and sends them to the `report-uri`. Report-only is the correct way to test a CSP on a production site before turning it on, so you catch false positives without breaking the user experience.

### What is a nonce and how do I use it?

A nonce is a random token, different on every page load, generated by your server. You put it in the directive as `script-src 'nonce-r4nd0m'` and as an attribute on the tags you want to authorise: `<script nonce="r4nd0m">`. The browser runs only the inline scripts carrying the current nonce. Since an attacker cannot predict the value, their injected script will not have it and stays blocked. It is the safe alternative to `'unsafe-inline'`.

### Is 'unsafe-inline' really that dangerous?

Yes, on `script-src` it defeats the protection. CSP works because it separates trusted code from arbitrary inline; `'unsafe-inline'` tells the browser to run any inline script, including the one injected by an XSS. In practice you are back to square one. If you have a lot of legacy `onclick=`, migrate gradually to nonces, hashes or addEventListener in external JavaScript instead of leaving the directive open.

### How do I stop my site from being loaded inside an iframe (clickjacking)?

With `frame-ancestors 'none'` in the CSP: no page, of any origin, will be able to embed yours in a frame or iframe. If you want to allow only your own origin, use `frame-ancestors 'self'`. This directive is more expressive and reliable than the old X-Frame-Options header, which only covered the DENY and SAMEORIGIN cases; having it is the modern clickjacking defense.

### Does default-src cover all the other directives?

Only the fetch directives you have not declared explicitly. `default-src` is the fallback for `script-src`, `style-src`, `img-src`, `connect-src`, `font-src` and similar when those are missing. But beware: some important directives do NOT fall back to default-src, notably `frame-ancestors`, `base-uri` and `form-action`. Always declare them separately, otherwise they stay unrestricted even with a tight default-src.

### How do I apply the CSP: HTTP header or meta tag?

The HTTP header is the recommended route: `add_header Content-Security-Policy "...";` in Nginx, `Header set Content-Security-Policy "..."` in Apache, or a middleware in your application framework. Alternatively you can use `<meta http-equiv="Content-Security-Policy" content="...">` in the HTML, but the meta tag has limits: it does not support `frame-ancestors` or `report-uri` and must sit before any resource. For complete protection use the header.

### What is 'strict-dynamic' and when is it worth it?

It is a keyword that propagates trust: if a script authorised with a nonce dynamically loads others, those inherit the authorisation without needing their hosts listed in the policy. It pays off on modern applications with bundlers and dynamic module loading, where maintaining a domain allowlist would be brittle. When `'strict-dynamic'` is present, hosts and wildcards in the same directive are ignored by browsers that support it.

### How do I test a CSP without breaking the site?

Start in report-only with an active `report-uri` and watch the real violations generated by user traffic for a few days. Each report names the violated directive and the blocked resource: use them to widen the policy only where needed, preferring nonces and specific hosts over wildcards. When the stream of legitimate violations dries up, replace the report-only header with the enforcing one. Regenerate the final version in the builder and revalidate it before deploying.

---
Tool by Maurizio Fonte, senior backend engineer. All tools: https://www.mauriziofonte.it/tools/
