Content Security Policy

June 25, 2026

Content Security Policy (CSP) allows us to define which resources are considered trusted on our website and which ones should be blocked. This helps strengthen our defenses against a wide range of attacks.

CSP

Even a static website can be abused by attackers in ways that may harm its users. That is why we should do everything reasonably possible to reduce the risks associated with executing untrusted code and the misuse of our website. One tool that can help us achieve this is Content Security Policy (CSP).


What Exactly Is CSP?

Content Security Policy (CSP) is a set of instructions sent to the browser that defines which resources a page is allowed to load and execute.

Different directives can be used to specify which sources are accepted for images, stylesheets, scripts, and other resources. For example:

Content-Security-Policy:
  default-src 'self';
  script-src 'self';
  style-src 'self';
  img-src 'self' data:;
  font-src 'self' https://cdn.trusted.com;
  connect-src 'self';
  object-src 'none';
  base-uri 'self';
  form-action 'self';
  frame-ancestors 'none';
  upgrade-insecure-requests;

  • default-src 'self'; – fallback policy for resource types that are not explicitly defined
  • script-src 'self'; – allowed JavaScript sources
  • style-src 'self'; – allowed stylesheet sources
  • img-src 'self' data:; – allowed image sources
  • font-src 'self' https://cdn.trusted.com; – allowed font sources
  • connect-src 'self'; – controls which endpoints JavaScript can communicate with using Fetch, XMLHttpRequest, WebSocket, or EventSource
  • object-src 'none'; – controls sources for <embed>, <object>, and <applet> content
  • base-uri 'self'; – restricts the document’s base URL
  • form-action 'self'; – restricts form submission targets
  • frame-ancestors 'none'; – controls which sites may embed the page
  • upgrade-insecure-requests; – automatically upgrades HTTP requests to HTTPS

These rules are typically delivered through an HTTP response header when we have access to the web server configuration. A common example is configuring CSP in Nginx.

However, CSP can also be defined through a <meta> tag when working with a static site and we do not control the server configuration. It is important to note that some directives (such as frame-ancestors) are not supported when CSP is delivered via a meta tag.


How Can You Check Which CSP Rules a Website Uses?

  1. Open the browser’s Developer Tools and navigate to the Network tab.
  2. Select the main document request and inspect the Response Headers section for a Content-Security-Policy header.
  3. If no CSP header is present, check the page source for a CSP <meta> tag.

Hashes and Nonces

If we need inline scripts or styles but still want to prevent unauthorized code execution, there are two common approaches available.

  • If the code is static and does not depend on dynamic data, we can use a hash. The hash value is specified in the CSP policy, and the browser compares it against the hash of the inline script or style. No additional attribute is required on the script tag itself. This allows only the approved inline code to execute. The downside is that even a single-character change requires the hash to be regenerated. Fortunately, many modern tools automate this process. For example, Astro can generate the required hashes automatically when CSP support is enabled.

  • A nonce (“number used once”) is a unique value generated by the server for each page load. The same nonce must be included both in the CSP header and in the nonce attribute of the corresponding script or style tag. This approach is useful when the content is dynamic and cannot be hashed ahead of time. Because the nonce must be generated at request time, this solution requires server-side processing and is generally not suitable for purely static sites served directly from a CDN.


Modern CSP configurations typically favor nonce- or hash-based approaches because they provide stronger protection against XSS attacks than simple domain allowlists.


What Types of Attacks Can CSP Help Mitigate?

Covering every possible attack vector would be difficult, but let’s look at some common examples.


XSS (Cross-Site Scripting)

XSS occurs when malicious code is injected into a website and executed in the context of another user’s browser. There are several variants of this attack, but they all aim to execute unauthorized code on behalf of the victim.

Supply Chain Compromise

A dependency may become compromised and introduce malicious code into your application. With a hash- or nonce-based CSP, an unexpected modification to a script may no longer satisfy the CSP requirements, causing the browser to block its execution.

Markdown Content Injection

Some Markdown renderers allow raw HTML. If this content is not properly sanitized, it can introduce XSS vulnerabilities.

User Input Compromise

Malicious code may be stored in a database if user input is not properly validated or sanitized. For example, a commenter could submit a malicious script, and the application might later render it for every visitor. In such cases, CSP serves as an additional layer of defense, but it should never be considered a replacement for proper input validation and output encoding.


Clickjacking

Clickjacking occurs when an attacker embeds a legitimate page inside an invisible frame and tricks users into interacting with it. The victim may believe they are clicking a harmless button, while in reality they are approving a transaction or performing another unintended action.

The frame-ancestors directive helps protect against this attack. If you do not intend your site to be embedded in frames at all, setting it to 'none' is generally a good choice.

Because frame-ancestors is not supported in meta-tag CSP, it must be delivered through an HTTP header. Alternatively, the X-Frame-Options header can be used, for example through a _headers configuration file.


Base Tag Hijacking

If an attacker manages to modify the href attribute of the document’s <base> tag, all relative links on the page may be redirected to a malicious destination. The base-uri 'self' directive helps prevent this.


Form Action Hijacking

An attacker may attempt to alter the target URL of a form submission. The form-action directive can restrict where forms are allowed to submit data.


Data Exfiltration

Data exfiltration refers to the unauthorized transfer of data from a website or application. The connect-src directive can restrict which external endpoints JavaScript is allowed to communicate with through Fetch, XMLHttpRequest, WebSocket, or EventSource.


Legacy Plugin Exploits

Although technologies such as Flash and Java Applets are effectively obsolete today, using object-src 'none' remains a good practice because it completely disables embedded plugin-based content.


Don’t Forget the Exceptions

A CSP that is too restrictive can easily break legitimate functionality. For that reason, it is often best to start with the Content-Security-Policy-Report-Only header.

This mode allows you to test your policy without actually blocking scripts or styles. Instead, violations are reported through browser developer tools and can also be sent to reporting endpoints if configured.

Only switch to an enforcing policy after reviewing and addressing all violations related to legitimate functionality.

For example, if you use Sentry for error monitoring or Google Analytics for traffic analysis, make sure the required domains are included in your policy.


Evaluating Your HTTP Security Headers

You can use Mozilla Observatory to evaluate the strength of your site’s security-related HTTP headers and overall configuration.

The Benchmark Comparison section can also be useful for comparing your score against other websites.

You may be surprised by how many well-known sites do not achieve top ratings. In practice, security often requires trade-offs. During one of my evaluations, even Google Search did not receive a particularly high score, which illustrates how complex applications sometimes prioritize functionality and flexibility over strict security policies. Of course, these results may change over time.


Conclusion

Overall, I believe it is worth spending a few hours implementing CSP as an additional layer of defense for your website.

If the site is already in production, introduce rules gradually and use Report-Only mode first to verify that important functionality is not accidentally broken before enforcing the policy.