Content Security Policy Guide: Build, Test, and Maintain Safer HTML Pages
CSPweb securityHTMLHTTP headersbrowser debuggingfrontend development

Content Security Policy Guide: Build, Test, and Maintain Safer HTML Pages

CCloud Dev Toolbox Editorial Team
2026-11-07
7 min read

Learn how to add, test, troubleshoot, and maintain a Content Security Policy for HTML pages with practical directives and header examples.

A Content Security Policy (CSP) gives the browser a clear list of permitted scripts, styles, images, fonts, connections, and frames. This guide explains how to add a CSP to an HTML website, test it safely in report-only mode, diagnose blocked resources, and maintain the policy as your frontend changes.

Overview

CSP is an HTTP response header that reduces the risk of unwanted content executing in a page. It is especially useful for limiting the impact of cross-site scripting and controlling which external services a page may contact. CSP does not replace output escaping, input validation, secure cookies, access controls, or other defensive measures. Treat it as one layer in a broader HTML security strategy.

For a deployed HTML site, the preferred starting point is an HTTP header such as Content-Security-Policy. A policy can also be placed in a <meta http-equiv="Content-Security-Policy"> element, but a meta policy has limitations and cannot provide every header-level control. In particular, controls that affect framing or reporting may require an HTTP response header. Configure CSP at the hosting, reverse-proxy, CDN, or application layer whenever possible.

A restrictive policy is built around default-src, then refined with directives for particular resource types. A simple starting point for a mostly self-hosted static page is:

Content-Security-Policy: default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'

This example allows resources from the site’s own origin by default, blocks legacy plugin content, limits the document base URL, and restricts which pages may embed the document. Review the effect in a test environment before applying it to production. If the page uses inline scripts, third-party analytics, web fonts, APIs, embedded video, or payment widgets, those dependencies must be handled deliberately rather than added broadly to make errors disappear.

For related deployment checks, see the guide to deploying a static HTML file to the cloud. You can also use the HTML validation and debugging workflow to inspect browser console messages and network requests before changing the policy.

Checklist by scenario

Starting a new static HTML page

  1. List every resource the page loads: scripts, stylesheets, images, fonts, media, frames, workers, and API endpoints.
  2. Identify whether each resource is same-origin, hosted on a trusted external origin, or generated inline.
  3. Start with a narrow policy rather than allowing every source.
  4. Set object-src 'none' unless the site has a documented need for plugin content.
  5. Set base-uri 'self' or base-uri 'none' when the application does not use a custom base URL.
  6. Test the page with browser developer tools open and record blocked requests.

Adding scripts or a frontend build

For external scripts, use a specific origin in script-src only when the dependency is required. An origin such as https://cdn.example.com allows resources from that host, so confirm that the scope is appropriate. Prefer a build that emits external files from a controlled origin. For inline scripts, a nonce or cryptographic hash is generally more precise than allowing all inline code with 'unsafe-inline'. A nonce must be unpredictable, generated for the response, and applied to both the policy and the intended script element.

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

Do not copy a fixed nonce into a static file and reuse it indefinitely. If a static deployment cannot generate response-specific nonces, move inline code into external files or use carefully managed hashes where the exact content is stable.

Loading styles, fonts, and images

Use separate directives so each resource type has an intentional source list. A common pattern is:

default-src 'self';
style-src 'self';
font-src 'self';
img-src 'self' data:;
media-src 'self';
object-src 'none';

Only include data: or blob: when the application needs those schemes. They can be useful for generated images or browser-created files, but broad scheme allowances make the policy harder to review. If fonts or images come from a content delivery network, add the exact trusted origin to the relevant directive instead of placing it in default-src without a reason.

Calling an API or connecting to a third-party service

Use connect-src for fetch requests, XMLHttpRequest, WebSocket connections, and similar browser connections. For example:

default-src 'self';
connect-src 'self' https://api.example.com wss://stream.example.com;
script-src 'self';
style-src 'self';
object-src 'none';

CSP controls whether the browser permits the connection; it does not replace server-side CORS configuration. If a request still fails, inspect both the CSP console message and the response’s CORS headers. The CORS troubleshooting checklist can help separate these two failure sources.

Embedding or allowing frames

Use frame-src to control frames your page loads, such as an embedded video or payment component. Use frame-ancestors to control which sites may embed your page. These are different questions:

frame-src 'self' https://player.example.com;
frame-ancestors 'self';

Do not add a frame provider to frame-ancestors merely because your page embeds that provider. Add it only when the provider or another trusted site must embed your page.

Testing before enforcement

Use Content-Security-Policy-Report-Only to observe violations without blocking resources:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; style-src 'self'; object-src 'none'; base-uri 'self'

Visit important routes, submit forms, sign in, open menus, load lazy content, and exercise third-party integrations. Review browser console reports and any configured reporting endpoint. Report-only mode is a discovery step, not protection: it records what would be blocked while allowing the page to run normally. Once expected violations are resolved or intentionally accounted for, move the tested policy to Content-Security-Policy.

What to double-check

Resource or behaviorDirective to reviewDiagnostic question
JavaScript files and inline codescript-src, script-src-elemIs the script external, nonce-authorized, hash-authorized, or unintentionally inline?
CSS files and style attributesstyle-src, style-src-attrAre styles coming from the expected origin, and is inline styling genuinely required?
Images and faviconsimg-srcAre remote image hosts, data URLs, or blob URLs required?
Fontsfont-srcDoes the font host match the URL requested by the browser?
Fetch, API, and WebSocket trafficconnect-srcAre all environments, ports, and connection schemes represented?
Framesframe-src, frame-ancestorsAre you controlling loaded frames and page embedding separately?
Workers and manifestsworker-src, manifest-srcDo service workers, web workers, or an app manifest need their own source?
Forms and navigationform-action, navigate-to where supportedCan submissions or navigations go only to intended destinations?

Read the exact blocked URL in the console rather than guessing from the page source. A redirect can change the final origin, and a browser may classify a request differently from what its HTML element suggests. Test a production-like build because development servers, source maps, hot-module reloaders, and local proxies often introduce sources that do not exist in the deployed page.

Common mistakes

  • Using * everywhere: This removes much of the value of a source restriction. Name the origins and schemes the page actually needs.
  • Adding 'unsafe-inline' as the first fix: It may silence inline-script or inline-style violations, but it weakens the policy. Prefer external files, nonces, or hashes where practical.
  • Confusing CSP with CORS: CSP is a browser-side content loading policy. CORS is a server response mechanism for cross-origin requests. A successful CORS configuration does not automatically satisfy CSP.
  • Testing only the home page: Authentication screens, checkout flows, admin pages, error pages, and lazy-loaded components often use different resources.
  • Ignoring redirects: A permitted hostname may redirect to another host that is not permitted. Inspect the complete request chain.
  • Placing sensitive data in a report endpoint: Treat violation reports as potentially informative about URLs and page behavior. Protect, limit, and review any collection service.
  • Assuming a meta tag is equivalent to a header: Use the server-delivered header for the complete policy and for controls that meta delivery cannot provide.

When to revisit

Revisit the CSP whenever the underlying inputs change. That includes adding an analytics provider, changing a CDN, introducing a new API, moving fonts, enabling a payment widget, adding a service worker, or changing the frontend build pipeline. Also review it before a major release, after a hosting or DNS migration, and during routine seasonal planning cycles when third-party integrations are commonly updated.

Keep a short inventory beside the policy: each directive, its approved origins, the feature that requires it, and the person or team responsible for reviewing it. Remove sources that are no longer used. Run report-only testing against key user journeys, compare violations with the inventory, and then enforce the smallest policy that supports the tested application.

Finally, check the response in the deployed environment rather than relying only on a local file or development server. Confirm that the header is present on the relevant document responses, that caching does not serve an outdated policy, and that browser console errors have been evaluated rather than automatically suppressed. This repeatable review makes CSP maintenance part of normal web app debugging instead of a one-time header change.

Related Topics

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

Cloud Dev Toolbox Editorial Team

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.