The mechanics of CSP and the risk of breakage
A Content-Security-Policy (CSP) is an HTTP header or meta tag that informs the browser which dynamic resources are permitted to load and execute on a page. By defining a strict boundary around trusted origins and execution contexts, CSP serves as a vital defense-in-depth layer against cross-site scripting (XSS), clickjacking, and cross-site leaks.
However, implementing a strict CSP without prior observation often causes immediate site breakage. Modern web applications frequently rely on inline scripts, dynamically generated styles, and third-party integrations (like analytics or support widgets) that a newly enforced CSP will block by default. When resources are blocked, the browser simply refuses to load them, leading to broken functionality or blank screens for the user. Because these failures happen entirely client-side, the server has no native visibility into the breakage unless a reporting mechanism is actively configured.
Using Report-Only mode for safe observation
To mitigate the risk of breakage, CSP provides the `Content-Security-Policy-Report-Only` header. This header accepts the exact same syntax as the enforcement header, but instead of blocking policy violations, the browser merely reports them.
When a violation occurs in Report-Only mode, the browser prints an error to the developer console and, if configured, sends a JSON payload describing the violation to a specified endpoint. This allows engineers to deploy a strict policy to production, observe exactly what it would have blocked, and adjust the policy or refactor the application code before moving to active enforcement.
It is standard practice to run both an enforced, relaxed policy and a strict `Content-Security-Policy-Report-Only` policy simultaneously. This pattern ensures baseline protection while gathering data to safely tighten the enforcement over time.
Setting up modern CSP reporting
Effective observation requires configuring the browser to send violation reports to a centralized collection service. In CSP Level 3, the `report-to` directive is the standard mechanism for this, referencing an endpoint defined in the `Reporting-Endpoints` HTTP response header.
For backward compatibility with older browsers, the deprecated `report-uri` directive should be declared alongside `report-to`. Browsers that support `report-to` will prioritize it and ignore `report-uri`. A typical reporting configuration looks like this:
`Content-Security-Policy-Report-Only: default-src 'none'; report-uri https://example.com/csp-reports; report-to csp-endpoint`
The corresponding `Reporting-Endpoints` header must also be present in the response: `Reporting-Endpoints: csp-endpoint="https://example.com/csp-reports"`.
Implementing a strict CSP with hashes
Historically, CSPs relied on complex allowlists of domains, which proved difficult to maintain and vulnerable to bypasses if a trusted domain hosted JSONP endpoints or angular libraries. Current best practice favors a "Strict CSP" using either cryptographic nonces or hashes combined with the `strict-dynamic` directive.
When inline scripts are unavoidable, the `script-src 'hash_algo-hash'` directive allows specific scripts to execute based on their content. For example, if the browser encounters a blocked inline script, the console error will display the required hash, such as `sha256-V2kaaafImTjn8RQTWZmF4IfGfQ7Qsqsw9GWaFjzFNPg=`.
Using hashes requires caution: modifying even a single whitespace character within the script tag will alter the hash, causing the browser to block the script. This makes hashes suitable for static, unchanging inline scripts, but brittle for dynamically generated content where nonces are required.
A concrete CSP testing matrix
A safe rollout requires a structured testing matrix to verify that the policy behaves as intended. This matrix should cover common resource types and execution contexts. The following synthetic examples assume an enforced policy of `Content-Security-Policy: default-src 'none'; script-src 'self' 'sha256-abc...'; style-src 'self'; img-src 'self';`.
- Internal Script (`<script src="/assets/versioned/app.9bf3784686d7eaf9.js"></script>`): Expected Allowed. Matches `script-src 'self'`.
- External Script (`<script src="https://cdn.example/lib.js"></script>`): Expected Denied. Blocked by `script-src` falling back to `default-src 'none'`.
- Hashed Inline Script (`<script>console.log('test');</script>` where hash matches 'sha256-abc...'): Expected Allowed. Matches the explicit hash.
- Un-hashed Inline Script (`<script>alert('xss');</script>`): Expected Denied. Blocked due to missing `'unsafe-inline'` and no matching hash.
- Inline Event Handler (`<button onclick="doSomething()">`): Expected Denied. Blocked as inline execution without `'unsafe-inline'`.
- Data URI Image (`<img src="data:image/png;base64,...">`): Expected Denied. Blocked by `img-src 'self'` (Data URIs are not 'self').
Refactoring code for CSP compliance
Enforcing a strict CSP often requires refactoring existing frontend code. The most common necessary change is removing inline scripts and inline event handlers.
Instead of embedding JavaScript directly in HTML (`<script>var foo = "314"</script>`), the code should be extracted to a separate file (e.g., `app.js`) and loaded via a `src` attribute. This aligns with the `'self'` directive and improves maintainability.
Similarly, inline event handlers like `<button id="btn" onclick="doSomething()">` must be replaced. The logic should be moved to an external script using standard event listeners: `document.getElementById("btn").addEventListener('click', doSomething);`. Furthermore, text-to-JavaScript functions like `eval()` must be eliminated, as they are blocked unless the highly dangerous `'unsafe-eval'` directive is permitted.
Sources and further reading
Drafted with Gemini and checked for source links, advisory identifiers, structure and unsupported product claims. Examples are synthetic and are not scan results.
Drafted with Gemini and checked for source links, advisory identifiers, structure and unsupported product claims. Examples are synthetic and are not scan results.
Prepared with AI assistance and published by WarHound AI. Check the references and evidence limits in each article.
The WarHoundAI journalQuestions or a factual correction? Get in touch with the article URL and supporting evidence.