CSP testing checks whether the Content Security Policy delivered to a browser matches your intended restrictions and lets real website tasks work. Inspect the deployed document response, collect violations in report-only mode, repair the responsible dependency or directive, then repeat the same tasks with enforcement enabled.
A header can exist while a menu fails to initialize or a form cannot reach its API. This guide covers that gap for developers launching a portfolio, documentation site, or public application page. Use the website security headers checklist for the other response headers; use this workflow to test CSP behavior.
CSP testing checklist
Before enforcing a new policy:
- Record the final document URL and every delivered CSP header.
- Inventory the scripts, styles, fonts, images, connections, and frames required by each critical task.
- Send the candidate policy through
Content-Security-Policy-Report-Onlyand configure reporting. - Exercise initial load and delayed interactions in supported browsers.
- Group violations by route, effective directive, resource, and policy disposition.
- Remove unnecessary dependencies or make narrowly reviewed policy changes.
- Enable enforcement in a controlled environment and repeat the tasks.
- Keep the policy version, test evidence, and rollback procedure with the release.
Treat a clean run as evidence for the tested paths. A page-load check does not cover a payment frame opened later, a consent-dependent analytics request, or an authenticated route.
Inspect the delivered policy with a GET request
Use the actual public route rather than a local configuration file. Fetch the document with GET, follow redirects, and save its response headers:
curl -sS -L -D /tmp/csp-headers.txt -o /tmp/csp-page.html \
https://example.com/docs
cat /tmp/csp-headers.txt
The header file contains redirect responses as well as the final response. Identify the last document response and inspect it in the browser Network panel too. A header on a redirect is not evidence of the final HTML policy. GET also avoids assuming your host handles HEAD exactly like document navigation.
Look for both Content-Security-Policy and Content-Security-Policy-Report-Only. Inspect the HTML for a CSP meta declaration. Record which layer owns each value: application middleware, static hosting rules, or the CDN.
Multiple enforcing policies apply together. A permissive second header does not cancel a restrictive first one. For example, an application policy that permits an API cannot override an edge policy containing connect-src 'none'. MDN's CSP header reference explains these combined restrictions.
When production differs from a preview, compare delivered policies before editing application code. Also verify route-specific responses: a working homepage says little about a documentation route served by different middleware.
Start with CSP report-only and a resource inventory
A report-only policy describes violations without blocking resources because of that candidate policy. Existing enforcing policies still apply. Report-only is an observation phase, not protection equivalent to enforcement, and it must be sent as a response header rather than a meta element. See MDN's report-only reference.
For a small documentation site with external script and stylesheet files on its own origin, a candidate might be:
Reporting-Endpoints: csp="https://example.com/csp-reports"
Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; report-to csp
This is an illustrative resource policy, not a universal secure configuration. It will report inline scripts, inline styles, and cross-origin dependencies. Review framing and form destinations separately; their directives are not covered by this example.
Create the reporting endpoint before relying on it. The report-to name must match the endpoint name in Reporting-Endpoints. Browser support varies, so verify delivery in your target browsers. MDN's report-to documentation describes the modern report format and the deprecated report-uri fallback for browsers that need it.
Keep the collector bounded and treat reports as untrusted input. Avoid retaining full URLs with sensitive query strings; define redaction, retention, and ownership before production collection. A reporting URL with no working receiver gives you no useful server-side evidence.
Build the resource inventory while completing the task. For each dependency, write down the origin, resource type, initiating code, purpose, and owner. Include code splitting, lazy images, consent states, and embedded services. Remove unused integrations instead of allowing every domain observed in a noisy session.
Read CSP violations before changing the source list
A useful violation record identifies the document, the effective directive, the resource or inline operation, and whether the policy was enforcing or reporting. SecurityPolicyViolationEvent exposes fields including effectiveDirective, blockedURI, originalPolicy, and disposition.
For example, a documentation page might report a request to https://api.example.com/search under connect-src. If search is a supported feature, check who owns that endpoint and permit it under the connection directive after review. Adding the origin to script-src would not fix the connection failure. MDN's connect-src reference lists the script interfaces it controls, including fetch and WebSocket connections.
Use Console and Network together to trace the initiating component. Group repeat messages rather than treating ten identical failures as ten independent fixes. Keep report-only and enforcement records separate so the reviewer knows whether the visitor actually lost functionality.
If no policy-specific evidence exists, investigate other failure types. A network response with 500, a CORS rejection, and an insecure HTTP subresource need different repairs. The JavaScript console errors guide covers runtime triage, and the mixed content troubleshooting guide covers insecure dependencies on HTTPS pages.
Fix the directive or dependency that caused the failure
Choose the smallest change that supports the approved task:
- Unexpected external code: remove the integration or replace it with an owned dependency.
- Required API connection: review its exact origin and connection scheme under
connect-src. - Inline script: move static code to an external file or use a reviewed nonce or hash strategy.
- Inline style or generated style tag: check how the framework emits CSS and whether its CSP integration supports the chosen strategy.
- Missing image or font: review the resource URL, its directive, and the fallback policy before adding a source.
- Conflicting edge policy: repair the response layer that supplies the restriction rather than adding another header.
default-src supplies fallbacks for fetch directives, but an explicit directive uses its own source list. For instance, default-src 'self' does not automatically add 'self' to an explicit script-src https://cdn.example.com. MDN's default-src reference explains that replacement behavior.
For nonce-based scripts, generate an unpredictable value per response and use the matching value in the policy and approved script elements. Do not copy a fixed nonce into a static example and ship it. Hash-based policies need to match the exact script content. MDN's script-src reference covers those mechanisms.
Avoid adding *, broad schemes, or 'unsafe-inline' simply to silence Console. Also avoid assuming a script nonce fixes styles: style-src has its own rules, including differences between style elements and attributes. Retest the specific operation after every policy change.
Add a focused Playwright CSP test
Install the violation listener before navigation. A listener added after page load can miss the policy failure that prevented startup. This example collects main-document events during a documentation task that stays on the same document:
import { expect, test } from "@playwright/test";
type CspRecord = {
directive: string;
resource: string;
disposition: string;
};
type CspWindow = Window & { cspRecords: CspRecord[] };
test("documentation task produces no CSP violations", async ({ page }) => {
await page.addInitScript(() => {
const state = window as CspWindow;
state.cspRecords = [];
document.addEventListener("securitypolicyviolation", (event) => {
state.cspRecords.push({
directive: event.effectiveDirective,
resource: event.blockedURI,
disposition: event.disposition,
});
});
});
const response = await page.goto("https://example.com/docs");
expect(response).not.toBeNull();
const headers = await response!.allHeaders();
// During rollout, require the candidate's report-only header.
// After promotion, require content-security-policy instead.
expect(headers["content-security-policy-report-only"]).toBeTruthy();
// Replace these controls with your route's real public task.
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
await page.getByRole("button", { name: "Show installation steps" }).click();
await expect(page.locator("#installation-steps")).toBeVisible();
const records = await page.evaluate(() =>
(window as CspWindow).cspRecords
);
expect(records, JSON.stringify(records, null, 2)).toEqual([]);
});
Playwright's addInitScript documentation describes its execution before page scripts. Keep CSP bypass disabled in the test configuration. Pair the header-presence assertion with your reviewed expected policy value; presence alone cannot detect a weakened policy.
This event snapshot covers a finite interaction window. It does not collect worker violations or read records from child frames, and a full document navigation resets the array. Test additional routes separately, add explicit frame coverage for embedded tasks, and keep server reports for delayed behavior. Use task-specific completion assertions rather than an arbitrary sleep to decide when to inspect evidence.
Run the task across supported browsers and relevant consent or account states. Retain traces on failure and add the case to your website regression testing routine. Do not add a broad ignore rule when the test fails; identify the resource and owner first.
Promote the policy and check the public release
Once the candidate's required tasks pass, enable the reviewed enforcing policy in an environment you control. Repeat direct loads, client-side transitions, delayed panels, and embedded flows. Keep the previous reviewed header configuration available for rollback and monitor violations after release. Reporting and enforcement can coexist when you are evaluating a later policy revision.
Then run Slop Audit on the deployed public URL. It checks whether the observed response has an enforcing Content-Security-Policy header and records bounded same-site console or page errors and failed requests during browser load. Use the report to catch a missing header or surrounding runtime regression, assign the finding, and rescan after repair.
Slop Audit does not validate CSP directive strength, collect dedicated CSP violation events or reports, or exercise every interaction. A report-only header alone does not pass its enforcing-header presence check. Keep the focused browser tests and policy review alongside the audit; a passing scan does not establish that the policy is secure or complete.
Save the final URL, build identifier, exact policies, tested tasks, browsers, and remaining coverage gaps. That gives the next release a concrete policy contract to compare.