Content Security Policy Setup and Testing Guide for Modern Web Apps
cspweb-securitysecurity-headersfrontendbrowser-debuggingxss-protection

Content Security Policy Setup and Testing Guide for Modern Web Apps

CCloud Dev Toolkit Editorial Team
2026-11-13
7 min read

A practical CSP guide covering report-only rollout, header examples, violation fixes, third-party integrations, and an evergreen maintenance checklist.

A Content Security Policy (CSP) can reduce the risk of script injection, but introducing one carelessly can also block legitimate application behavior. This guide provides a reusable rollout checklist, practical CSP header examples, a report-only workflow, and troubleshooting steps for scripts, styles, analytics, APIs, fonts, images, and third-party integrations.

Overview

CSP is a browser-enforced set of rules that controls where a web page may load content from and which types of content it may execute. The policy is delivered with the Content-Security-Policy HTTP response header. A related Content-Security-Policy-Report-Only header lets you observe violations without blocking the affected resources.

A useful CSP rollout has two goals: reduce unnecessary trust in external or inline content, and preserve the application behavior users need. Start by mapping the application’s resource dependencies rather than copying a restrictive example into production. Modern web applications may load JavaScript modules, CSS, fonts, images, API requests, frames, workers, analytics scripts, payment widgets, support tools, and media from different origins. Each dependency should be deliberate and documented.

CSP is a browser control, not a replacement for output encoding, input validation, safe templating, dependency review, access control, or secure secret handling. It is one layer in a broader web security strategy. For related API debugging, see the CORS troubleshooting guide; CORS and CSP solve different problems and should not be treated as interchangeable.

A practical directive reference

DirectiveControlsTypical starting point
default-srcFallback for many fetch directives'self'
script-srcJavaScript, including permitted script origins and execution methods'self' plus approved nonces, hashes, or providers
style-srcStylesheets and permitted inline style behavior'self' plus a controlled nonce or approved style origin
connect-srcFetch, XHR, WebSocket, EventSource, and similar connectionsApplication API and approved service origins
img-srcImages and image data sources'self' data: only when data URLs are required
font-srcWeb fonts'self' and the font provider, if needed
frame-srcContent embedded in framesOnly explicitly approved frame origins
object-srcLegacy plugin content'none' unless a known requirement exists
base-uriAllowed document base URLs'self' or 'none'
frame-ancestorsSites permitted to embed the page'none' or a precise allowlist

Directive behavior varies by application architecture and browser support. Test the policy in the browsers and deployment environments your users rely on, especially when using newer CSP features or framework-specific nonce and hash support.

Checklist by scenario

Before writing the policy

  • Inventory every resource type: scripts, styles, fonts, images, media, frames, workers, manifests, and API connections.
  • Record the exact origins required by production, staging, authentication, payments, analytics, error monitoring, and customer-support integrations.
  • Inspect browser developer tools for loaded resources and existing violations. Test representative routes, not only the home page.
  • Separate required dependencies from optional or forgotten dependencies. A CSP allowlist is a security decision, not a complete inventory of everything the page happens to request.
  • Decide how the server or framework will generate per-response nonces if inline bootstrapping code must remain.

For a new application

  • Begin with default-src 'self' as a clear baseline, then define specific fetch directives where the application needs different sources.
  • Prefer external scripts or nonce- or hash-authorized scripts over broad inline execution allowances.
  • Set object-src 'none' unless the application has a verified reason to support plugin content.
  • Define base-uri and, where appropriate, frame-ancestors to reduce unexpected document behavior and framing.
  • Test login, checkout, file upload, media playback, account settings, and other flows that commonly use third-party resources.

For an existing application

  • Deploy an initial policy with Content-Security-Policy-Report-Only so violations can be observed without interrupting users.
  • Collect and group reports by directive, URL, route, release, and source location. Repeated violations may indicate one missing dependency or a recurring unsafe pattern.
  • Fix application code before adding broad exceptions. For example, move inline code into a bundled file or use a nonce rather than immediately adding 'unsafe-inline'.
  • Review third-party providers individually. An analytics provider may require more than one origin, and a provider’s documentation may change independently of your application.
  • Promote the policy to enforcement gradually, beginning with a representative route or a controlled deployment where rollback is available.

For a JavaScript-heavy frontend

  • Confirm whether the framework uses inline bootstrap code, dynamic imports, workers, style injection, or runtime evaluation.
  • Pass a server-generated nonce to the framework in the supported configuration rather than hard-coding one in source control.
  • Check connect-src for API hosts, WebSockets, telemetry endpoints, and development-only hot-reload connections.
  • Check worker-src when the application uses web workers or service workers. If it is absent, fallback behavior may depend on other directives and the browser.
  • Test both the production build and local development workflow; they often have different resource origins and execution patterns.

Example starting headers

A conservative starting point for a simple same-origin application might look like this:

Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data:; font-src 'self'; connect-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

This is an example, not a drop-in production policy. If a page needs a nonce for an inline script, the server can emit a per-response value and include it in both the header and the matching element:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{random-per-response-value}'; object-src 'none'; base-uri 'self'
<script nonce="{random-per-response-value}">
  // Small, intentional bootstrap code
</script>

Do not reuse a nonce across responses or place a predictable value in a static configuration file. Hashes can be useful for stable inline content, but the hash must match the exact script or style content, including whitespace changes.

What to double-check

Report-only collection

Report-only mode is valuable for discovery, but it does not protect users from the resources described by violations. Keep the report-only period focused: capture enough real traffic to cover important routes and user journeys, then set a review date. If reporting is sent to an endpoint, protect that endpoint from excessive volume and avoid storing sensitive URL query strings or page data unnecessarily.

Common violation patterns

Browser message or symptomChecks and fixes
A script was blockedCheck script-src, the script’s actual origin, and whether the script is inline, dynamically inserted, or using a nonce or hash.
Inline script or style was blockedMove it to a bundled file, authorize the exact content with a nonce or hash, or redesign the code path. Avoid broad unsafe allowances as a first fix.
An API request failed after deploymentAdd the precise API, WebSocket, or telemetry origin to connect-src. Also check CORS, because CSP approval does not grant cross-origin permission.
Fonts or images disappearedInspect font-src or img-src, including redirects and data URLs. Permit only formats and origins the application actually requires.
A payment or sign-in widget brokeMap every frame, script, connection, and image origin used by the provider. A single provider may require several directive-specific entries.
Reports continue after a fixConfirm the new header reached the affected route, remove cached or duplicate headers, and check whether another page or deployment still emits the old policy.

Deployment and caching

Verify the header at the edge or final application response, not only in an application configuration file. Reverse proxies, CDNs, serverless functions, and framework middleware can add, replace, or cache headers unexpectedly. If a nonce is generated per response, ensure a cache does not serve one user’s HTML with another response’s nonce. Test redirects and error pages as well as successful document responses.

Common mistakes

  • Copying a policy without mapping dependencies. A short header can still be wrong for the application. Build the policy from observed, intentional behavior.
  • Allowing * everywhere. Broad wildcards reduce the value of source restrictions and can hide an incomplete dependency review.
  • Adding 'unsafe-inline' or 'unsafe-eval' as the first fix. These allowances may be required by a legacy dependency, but first investigate whether the code or build process can be changed.
  • Confusing CSP with CORS. CSP controls what the page may load or connect to from the browser’s perspective; CORS controls whether a server permits certain cross-origin browser requests to be read.
  • Trusting a token decoder or browser report as validation. Developer tools can help inspect behavior, but security decisions still require server-side review, correct deployment, and safe handling of sensitive data.
  • Enforcing too quickly. Switch from report-only to enforcement only after testing authenticated and less-frequent workflows, including administration and recovery paths.
  • Leaving policy exceptions undocumented. Record why each third-party origin, nonce, hash, or special directive exists. This makes future cleanup safer when a provider or frontend framework changes.

When to revisit

Treat CSP as maintained application configuration rather than a one-time security checkbox. Revisit the policy before a major frontend migration, after adding an analytics or payment provider, when changing authentication flows, and whenever scripts, API hosts, fonts, frames, workers, or content delivery paths change. Also review it after a CDN, reverse proxy, caching, or deployment-platform change.

A practical maintenance cycle is:

  1. Review the current header and its documented exceptions.
  2. Run key user journeys in a test environment and inspect blocked-resource messages.
  3. Compare report-only findings with the dependency inventory.
  4. Remove origins and exceptions that are no longer required.
  5. Test the proposed header in a controlled release.
  6. Confirm enforcement, caching, redirects, and error responses in production.

Browser behavior, framework defaults, and third-party integration requirements can evolve. Recheck implementation details when your stack changes and during planned security reviews. For a broader debugging workflow, the encoding and debugging toolkit can complement browser inspection, while the secrets management guide covers a separate but related area of secure deployment practice.

Final action: create a small inventory of the resources your application truly needs, deploy a report-only policy, test the important routes, and fix violations by narrowing code behavior before widening the allowlist. Once the findings are understood and documented, move to enforcement with a rollback plan.

Related Topics

#csp#web-security#security-headers#frontend#browser-debugging#xss-protection
C

Cloud Dev Toolkit Editorial Team

Technical Editors

Senior editor and content strategist. Writing about technology, design, and the future of digital media. Follow along for deep dives into the industry's moving parts.