Navigate this guide · 1,743 words
The essential signal
- CSP adds browser-enforced restrictions; it does not replace input handling and secure application code.
- Nonces must be unpredictable and fresh for each response; hashes suit unchanged script bytes.
- Report-only policies help test a rollout but do not block violations.
A browser needs a rule it can enforce
A website may intend to run only its own scripts, but intention is not a browser policy. Content Security Policy, or CSP, lets the site tell the browser which actions and resource loads are permitted. MDN describes it chiefly as a defense against cross-site scripting, where injected code runs in the site's context. The policy adds restrictions at the browser boundary. It is useful because it makes part of the trust decision explicit instead of relying entirely on the assumption that unwanted executable content will never reach a page.[1]
That extra boundary is not a replacement for handling untrusted input correctly. MDN recommends protecting against injection and deploying CSP as defense in depth. If a site accepts content from a URL, a comment or another external source, the application still has to ensure that content is handled safely. A policy can reduce some consequences of a mistake; it does not make every mistake harmless. Keep those responsibilities separate when describing the feature, otherwise a useful restriction becomes an inaccurate promise that the site is secure merely because a header exists.[1]

Directives divide the decision by resource type
A policy consists of directives separated by semicolons. Each directive controls a particular aspect of what the document may do. MDN gives examples such as script-src for JavaScript, style-src for styles and img-src for images. The special default-src directive provides a fallback for resource types whose corresponding fetch directive is not explicitly listed. This structure lets a site express different requirements for different resources rather than treating every asset as though it had the same origin, execution risk and operational purpose.[1]
For an illustrative reading site, images might come from a content service while scripts remain tightly controlled. That is a reason to define the resource rules separately, not to allow every resource from any location that happens to serve one useful image. Write down the legitimate resource paths before constructing the policy. The exercise often reveals forgotten scripts or embeds. The point is to make the dependencies understandable first; copying a long policy from an unrelated site can hide assumptions that do not match the document you actually intend to serve.[1]
Why a host list may not be enough
A location-based policy can allow resources from named hosts or from the document's own origin using the self keyword. That is a useful control, but a host name does not describe every script that might be served from that location. MDN recommends nonce- or hash-based strategies for a strict script policy. Those approaches identify authorized script execution more specifically. This is a change in how trust is expressed, not a claim that choosing a different syntax makes application vulnerabilities disappear or removes the need to review dependencies.[1]
When evaluating a policy, ask what would happen if an unwanted script URL still matched an allowed location. The answer depends on the policy and the site, so avoid broad assurances based only on the presence of an allowlist. For a design review, describe the intended script entry points and how the browser recognizes them. That explanation is more informative than saying that a site has a strict policy without showing what strict means. A security label should summarize a documented decision, not stand in for the decision itself.[1]

Nonces authorize scripts in one response
A nonce is an unpredictable value generated for a response and included both in the policy and on the script elements the server intends to authorize. The browser compares those values. MDN stresses that the nonce must be different for each response and must not be predictable. The value is not an account password or a permanent identifier for a script. Its role is to distinguish the scripts deliberately included in this particular response from executable content an attacker might manage to inject without knowing the selected value.[1]
This requirement has an architectural consequence. A server that uses a nonce must generate the value and insert it into the policy and the appropriate markup for each response. Reusing a constant string in a static HTML file does not satisfy the freshness requirement. The server should also avoid indiscriminately stamping the nonce onto every script element after untrusted content has been inserted, because that could authorize the very script the policy was meant to reject. The selection of trusted elements is part of the design, not an incidental formatting step.[1]
Hashes bind permission to script contents
A hash-based policy identifies allowed script contents through a supported cryptographic digest. MDN describes calculating the hash, encoding it in Base64 and including the result with the appropriate algorithm prefix. The browser hashes the script and compares the value. Unlike a nonce, the hash can stay unchanged while the script bytes remain unchanged. That makes this approach useful for static documents, where generating fresh response-specific markup may not be available and where the build process already knows the exact contents that will be published.[1]
The maintenance obligation is just as important as the initial setup. If the script contents change, recalculate the corresponding hash and update the policy. Treat the script and its authorization as one release unit rather than editing them independently. MDN also describes integrity requirements for external scripts in this approach. This is why a build-based workflow is often a better fit than manually copying digest strings between files. The relevant guarantee concerns matching content, not whether two files have similar names or whether a change seems too small to matter.[1]
Inline code and dynamic trust need care
With an applicable script policy, inline scripts and inline event handlers are not simply assumed to be allowed. MDN describes nonces and hashes as ways to authorize appropriate inline script elements. Broadly enabling unsafe-inline weakens the intended restriction, so it should not become an automatic response whenever a page breaks during testing. First identify which code is being blocked and why it is there. A rollout that resolves every error by adding broader permission can gradually erase the boundary the policy was introduced to establish.[1]
The strict-dynamic keyword changes trust propagation: a script authorized by a nonce or hash may load further scripts without those same explicit markings. That can simplify applications that depend on script loaders, but it also gives trusted code more responsibility. MDN warns about code that creates script elements from potentially unsafe sources. Before enabling that behavior, inspect how the loader chooses new URLs. A policy is only as meaningful as the trust decisions it delegates, and delegation should be a deliberate choice rather than a keyword added without understanding the resulting resource graph.[1]

Delivery method affects available features
MDN recommends delivering CSP through the Content-Security-Policy response header. A meta element can express a policy in some static-page situations, but it does not support every CSP feature. Do not treat a header configuration and a markup-only configuration as interchangeable without checking the relevant directive. This matters especially when the hosting platform limits response headers. The available deployment mechanism may constrain which policy you can actually implement, even if a copied example appears valid as text in a local HTML file.[1]
The distinction is particularly clear for report-only deployment, which MDN says cannot be delivered through a meta element. If your host does not offer the necessary header control, say so in the deployment notes and choose a supported arrangement. Do not claim that a meta tag has activated a reporting mode that browsers do not support there. A practical policy document should therefore name both its intended restrictions and how those restrictions reach the browser. That keeps configuration advice grounded in the behavior of the actual hosting environment.[1]
Report-only is a rehearsal, not enforcement
Content-Security-Policy-Report-Only lets a site observe violations of a proposed policy without blocking them. With an appropriately configured reporting endpoint, the resulting reports can help identify legitimate resources that the proposed rules would reject. This makes it a useful rollout tool, especially for a site with dependencies that are not yet fully inventoried. The important limitation is in the name: a report-only policy does not enforce those restrictions. Describing it as active blocking would give operators and readers a false understanding of the protection currently in place.[1]
An enforced policy and a report-only policy can coexist, allowing a site to test a future revision while retaining the current restrictions. Keep the two configurations clearly labeled. In a release checklist, record which header is enforcing rules and which is collecting observations. Review legitimate failures before promotion, and retain a way to diagnose unexpected blocking after the change. These are deployment practices suggested here, not a guarantee that an absence of reports proves the absence of injection risks or that every browser interaction has already been exercised.[1]
Different threats need different directives
CSP is not only about scripts. MDN describes frame-ancestors as a way to control which other documents may embed the protected document, helping address clickjacking. That is a different direction of control from deciding which resources the document itself may load. Keeping the direction straight prevents a frequent conceptual mistake: allowing an iframe resource and allowing another site to frame your page are not the same policy question. Read each directive in terms of the action and document relationship it governs before applying it.[1]
Likewise, upgrading insecure requests and requiring trusted types address additional use cases, rather than automatically following from a script rule. Choose controls for the problems actually present and verify their delivery requirements. A single policy string can contain several protections, but that does not make every protection implicit. In documentation, explain the intended boundary in ordinary language alongside the directive. Someone reviewing the configuration should be able to tell which risk it addresses and which responsibilities remain elsewhere in the application and hosting environment.[1]

Keep policy changes part of the release
A maintainable CSP follows the site's real resource graph. Inventory entry points, choose an authorization approach suited to the hosting model, test proposed restrictions and release the policy together with the code it authorizes. For a static site, that may mean recalculating script hashes during the build. For a dynamically rendered application, it may mean generating and attaching fresh nonces on every response. Neither approach should be described as a universal paste-in solution that removes the need to understand how the application assembles its pages.[1]
The goal is not to produce the longest header or the fewest console messages. It is to make legitimate execution explicit while denying unintended execution under a documented policy. When the site changes, revisit that policy rather than preserving stale permissions indefinitely. This makes CSP a practical part of engineering maintenance instead of a one-time badge. Its value is strongest when paired with safe input handling, reviewed dependencies and honest deployment notes that distinguish what is enforced today from what is merely being tested for a later release.[1]
Questions, answered.
Does report-only CSP block unwanted scripts?
No. It observes policy violations without enforcing the proposed restrictions. An enforcing Content-Security-Policy header is a separate configuration.[1]
Can a static file use one permanent nonce?
A permanent value does not satisfy the requirement for an unpredictable nonce fresh for each response. Hash-based policies are often more suitable for unchanged static scripts.[1]
Read the sources.
- Content Security Policy guideMDN Web Docs · Accessed 2026-10-14
AI assistance and source checks reduce risk; they do not guarantee error-free reporting. See our editorial policy.
