HeaderScan.dev
Tools

CSP implementation · Practical guide

How to roll out Content Security Policy with Report-Only

Use Content-Security-Policy-Report-Only to observe a candidate before it blocks resources. Start with one representative route, identify required dependencies, test the policy in a browser and then introduce enforcement. This workflow covers the header, HTML templates, reporting and deployment decisions that must agree.

Observe one page before enforcing its policy

Choose a staging page with a form and an inline script. Record any existing enforced policy, then send the candidate below as an HTTP response header. It would restrict resources to the same origin and reject inline scripts. Report-Only observes those violations; an existing enforced policy still applies.

Use DevTools Console for this first local check. No collection endpoint is configured, so expect no server reports; the browser may also warn about the missing endpoint. HeaderScan will still report missing enforcement when only Report-Only is present. It follows up to five redirects without cookies or JavaScript and redacts full CSP values.

# Candidate HTTP response header for a staging page
Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'
  1. Open Network, reload and select the final document request. Confirm its status, URL and Report-Only header before investigating violations.
  2. Exercise the form and inline-script feature. Observe the violated directive and resource in Console; confirm this candidate did not prevent the feature from running.
  3. Record each required dependency and its owner. Keep this candidate in Report-Only while implementing the allowances below.

Reference: MDN: Content-Security-Policy-Report-Only

Build a dependency inventory from real journeys

Include signed-in pages, consent choices, payment flows and lazy-loaded features. For each resource, record the route, action, origin, directive and business reason. A script may load successfully while its analytics request fails connect-src; allowing the script origin alone does not cover every request.

Reproduce unexpected violations in a clean browser profile with extensions disabled. Extension-injected code and abandoned integrations should not automatically become allowed sources. Group repeat findings by route, directive and resource, then fix the dependency or approve a narrow allowance. Zero reports do not prove coverage of an untested journey.

Build a dependency inventory from real journeys
DependencyPolicy to reviewConcrete check
External script script-src or an explicit script-src-elem Approve the required origin and verify its feature.
API or analytics request connect-src Submit the form and inspect the network response.
Styles, fonts and images style-src, font-src and img-src Check delayed content and all supported page states.
Embedded payment frame frame-src Complete the integration in its test environment.

Reference: MDN: CSP directives and deployment

Generate the nonce once and use it in both places

Save this standalone PHP 8 example as a staging demonstration page served by PHP. random_bytes(16) generates 128 cryptographically random bits; Base64 encoding makes the value suitable for the header and HTML attribute. The same value reaches both places in this response. For a framework application, integrate generation into its response middleware and trusted template instead. Never add a nonce indiscriminately to user-supplied script markup.

Cache-Control: no-store applies to this demonstration response to avoid reusing its nonce. It is not a global policy for static assets. Reload the page and check window.appReady in Console, then compare the actual document header with the response HTML.

Check the response source and header together, then request another response and confirm the nonce changes. A CDN must not combine a fresh header with cached HTML or reuse nonce-bearing HTML across visitors. For static cached pages, consider hashes for fixed scripts instead. Update those hashes when script content changes.

<?php
$nonce = base64_encode(random_bytes(16));
header("Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self' 'nonce-{$nonce}'; object-src 'none'; base-uri 'self'");
header('Cache-Control: no-store');
?>
<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>CSP nonce demonstration</title>
</head>
<body>
    <p>Inspect the document headers and this script in DevTools.</p>
    <script nonce="<?= htmlspecialchars($nonce, ENT_QUOTES, 'UTF-8') ?>">window.appReady = true;</script>
</body>
</html>

Reference: W3C CSP: nonce requirements and reuse

Connect reporting only after the collector works

Provision an HTTPS collector that accepts browser POSTs without an interactive login. Reporting API requests use application/reports+json; legacy report-uri requests use application/csp-report. Verify both payload formats if supporting both. Treat incoming reports as untrusted, limit request sizes and restrict access to logs containing page URLs.

Replace example.com and the collection path below, then append the reporting directives to your complete candidate policy, including any nonce. report-to selects the named Reporting-Endpoints destination. The deprecated report-uri provides a fallback for browsers without report-to support; browsers supporting report-to ignore report-uri. Deliberately trigger a staging violation and confirm collection, since delivery can be delayed or unavailable.

# Example only: these URLs must point to your working HTTPS collector
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: default-src 'self'; object-src 'none'; base-uri 'self'; report-to csp; report-uri /csp-reports

Reference: MDN: report-to and reporting compatibility

Enforce a tested candidate with a reversible deployment

Assign one configuration owner and retain the previous policy. Test enforcement on the staging route, including an intentionally unapproved inline script in its HTML. The approved script should run and the unapproved one should be blocked. Deploy a small route group, monitor real journeys and expand coverage deliberately.

If a legitimate feature breaks, restore the previous enforced policy at every layer that supplies it and keep the candidate in Report-Only for diagnosis. A second permissive policy cannot cancel an existing restrictive one. Refresh affected cached responses, preserve nonce/header alignment and verify the public response again. curl checks delivered headers, not browser enforcement.

# macOS/Linux: inspect the response after deployment or rollback
curl -q -sS -L --max-redirs 5 --max-time 30 \
  -D - -o /dev/null 'https://example.com/'

Reference: MDN: multiple CSP policies

Keep reading

Related guides

All guides & reference
Quick reference

HTTP response headers reference: security, cookies and caching

Look up common HTTP response headers, their purpose and the checks to make before changing a policy.

Open reference