Content Security Policy Guide: Build, Test, and Maintain a Safer CSP
CSPweb securityHTTP headersfrontend developmentbrowser debugging

Content Security Policy Guide: Build, Test, and Maintain a Safer CSP

CCode Craft Studio
2026-11-07
6 min read

Build and maintain a safer Content Security Policy with directives, Report-Only testing, nonce and hash examples, and troubleshooting steps.

A Content Security Policy (CSP) can reduce the impact of cross-site scripting and other injection problems, but an incomplete policy can also block legitimate scripts, styles, images, fonts, frames, and third-party services. This guide provides a practical checklist for designing CSP headers, testing them safely with Report-Only mode, troubleshooting browser violations, and maintaining the policy as a frontend changes.

Overview

CSP is an HTTP response header that tells a browser which sources are allowed for particular types of content. A policy can restrict executable JavaScript, stylesheet sources, images, fonts, network connections, embedded frames, form submissions, and more.

A basic policy might look like this:

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

This example is only a starting point. The correct policy depends on the application’s architecture, asset delivery, analytics, payment providers, content management system, and authentication flows. Treat a CSP checker or generator as an aid for reviewing syntax and sources, not as a substitute for understanding what the application loads.

Understand the main directives

DirectiveControlsTypical review question
default-srcFallback for many fetch directivesWhat should be allowed when a more specific directive is absent?
script-srcJavaScript and script-related sourcesCan scripts run only from trusted origins, nonces, or hashes?
style-srcStylesheets and permitted inline stylesDoes the UI framework require inline style support?
img-srcImages and image data sourcesDo uploads, CDNs, avatars, or data URLs need access?
font-srcWeb fontsAre fonts served locally or from a separate provider?
connect-srcFetch, XHR, WebSocket, and similar connectionsWhich API, telemetry, and real-time endpoints are required?
frame-srcOrigins that the page may embed in framesDoes the page embed video, payment, or support content?
frame-ancestorsSites allowed to embed the pageShould the page be frameable at all?
object-srcPlugin-style object contentCan this be disabled with 'none'?
base-uriAllowed base element URLsShould the document permit a custom base URL?
form-actionForm submission destinationsWhere may forms send user-entered data?

Use a specific directive when the application has a specific requirement. A broad default-src can provide a fallback, but it should not hide important differences between scripts, API connections, frames, and media.

Checklist by scenario

When creating a policy for a new frontend

  1. Inventory browser resources. Open the application in a clean browser session and record JavaScript bundles, CSS, fonts, images, API calls, WebSockets, frames, form destinations, and vendor integrations.
  2. Map each resource to a directive. Separate script origins from API origins. For example, an analytics endpoint belongs in connect-src when the browser sends data with a request; it does not automatically belong in script-src.
  3. Start with defensive defaults. Consider object-src 'none', a deliberate base-uri, and an appropriate frame-ancestors value. Add only the sources the application needs.
  4. Choose an inline-script strategy. Prefer nonces or hashes over broadly allowing inline script execution. Remove unnecessary inline handlers such as onclick where practical.
  5. Deploy in Report-Only mode. Use Content-Security-Policy-Report-Only while collecting violations and checking real user flows.

When the application uses server-rendered pages

Generate a fresh, unpredictable nonce for each response that contains an authorized inline script. Place the same nonce in the header and on the intended script element:

Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-{per-response-value}'; object-src 'none'; base-uri 'self'
<script nonce="{per-response-value}">
  startApplication();
</script>

The nonce must be handled as a server-side value and should not be reused as a permanent token. A nonce does not make every inline script safe; it authorizes scripts carrying that particular value, so template and response handling still require care.

When the application uses static assets

Hashes can authorize a specific inline script or style whose content is known and stable:

Content-Security-Policy: script-src 'self' 'sha256-{base64-encoded-hash}'

If the inline content changes, its hash must change too. This makes hashes useful for fixed bootstrap code but less convenient for templates that vary between responses. Build pipelines should calculate and update hashes consistently rather than relying on manual copying.

When adding a third-party vendor

  1. Identify the exact resources the vendor uses instead of allowing its entire domain family by default.
  2. Check whether it loads additional scripts, frames, fonts, images, or API endpoints from separate origins.
  3. Add the smallest required source list in Report-Only mode.
  4. Test the vendor’s essential user journey, including consent, login, payment, or embedded content where relevant.
  5. Document the business owner and reason for each external source.

For authentication-related debugging, separate CSP failures from token or server errors. A blocked API request may produce a browser console violation even when the endpoint itself would return a valid response. A separate JWT decoder workflow can help inspect token claims, but it cannot resolve a browser policy that prevents the request from being sent.

What to double-check

Test both allowed and blocked behavior

A policy is not complete when the homepage loads. Test authenticated and unauthenticated routes, form submissions, file uploads, lazy-loaded components, error pages, embedded media, web fonts, API calls, WebSockets, and any administrative interface. Use the browser’s developer tools to distinguish a CSP violation from a network failure, certificate problem, CORS response, or application error.

Review reporting safely

If reporting is configured, send reports to an endpoint that can accept malformed, duplicated, or high-volume input. Avoid placing sensitive request data or raw user content into logs without a retention and access plan. Group repeated reports by directive, blocked source, page, and release version so that a single noisy integration does not obscure a genuine regression.

Confirm that the intended header is present on the actual HTML response, not only on a development server or static asset. Check reverse proxies, CDNs, redirects, cached responses, and application-specific routes. A header added at one layer may be overwritten or omitted at another.

Also confirm that the policy matches the page’s framing and navigation requirements. frame-ancestors, form-action, and base-uri address different behaviors; adding one does not automatically enforce the others.

Common mistakes

  • Using 'unsafe-inline' as the final solution. It may make a migration easier, but it weakens the control over inline scripts or styles. Replace it with nonces, hashes, or externalized code where the application permits.
  • Allowing * broadly. Wildcards can conceal an expanding dependency list and may allow more origins than the feature needs.
  • Confusing CSP with CORS. CSP controls what the browser permits the page to load or connect to. CORS controls whether another origin may read a response. Both can affect an API workflow, but they solve different problems.
  • Testing only in development. Production may use different asset hosts, environment variables, redirects, feature flags, or vendor configurations.
  • Forgetting dynamic imports and lazy loading. A route that is not visited during initial testing may load a different JavaScript chunk or contact a different endpoint.
  • Copying a generated policy without review. A content security policy generator can reveal likely directives, but its output should be narrowed, documented, and tested against actual application behavior.
  • Ignoring inline event handlers. Moving a script tag to an approved source does not automatically authorize HTML attributes that execute code.

When to revisit

Keep this guide as a release checklist rather than treating CSP as a one-time security task. Revisit the policy before a major frontend deployment, after changing bundlers or rendering methods, when adding a CDN or vendor, and whenever the application introduces a new API, frame, font, analytics system, or payment flow. Seasonal planning cycles are also useful checkpoints for reviewing integrations that may be enabled only during a campaign or publishing period.

When a violation appears, use this short workflow:

  1. Reproduce the affected page and record the exact directive, blocked URL, document URL, and release version.
  2. Decide whether the request is expected. Do not allow a source solely because it appears in a report.
  3. Identify the narrowest directive and source expression that satisfies the feature.
  4. Add the change to Report-Only mode first and test the complete user journey.
  5. Review report volume and false positives, then promote the tested policy to enforcement.
  6. Document why the source is present and assign an owner for future review.

Finally, recheck your policy whenever browser behavior, framework defaults, deployment tooling, or third-party integrations change. A useful CSP is maintained alongside the frontend: explicit enough to guide the browser, flexible enough to support real features, and reviewed whenever the application’s inputs change.

Related Topics

#CSP#web security#HTTP headers#frontend development#browser debugging
C

Code Craft Studio

Technical Editor

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.