
Content Security Policy (CSP) Implementation guide: Protecting web applications from XSS attacks
Looking to implement Content Security Policy for your website? This comprehensive CSP tutorial covers everything from basic CSP directives to advanced configuration examples, helping you secure your web applications against XSS attacks and other security threats.
Content Security Policy (CSP) is a powerful web security feature designed to prevent a wide range of cyber attacks, including Cross-Site Scripting (XSS), clickjacking, and data injection. It acts as a set of rules that tells the browser what content is allowed to be loaded, and how. By explicitly specifying trusted sources for scripts, styles, media, and other resources, CSP helps developers control the execution environment of their applications, limiting the risk of malicious content being injected and executed. As browsers become more security-conscious and attackers more sophisticated, learning how to implement CSP has become an essential skill.
Just as airport security enforces strict rules about what items passengers can bring onboard, like banning large liquids or restricting access to secure areas, CSP enforces rules about what content a browser can load or execute on a webpage. In this analogy, the browser acts like airport security, inspecting every “item” (script, image, style, etc.) trying to board the “flight”—the website. Only resources from trusted, pre-approved sources are allowed through, helping prevent dangerous payloads (like malicious scripts) from slipping past unnoticed. Just as the safety of a flight depends on the thoroughness of airport security, the security of a website relies on the strictness and clarity of its CSP.
CSP is difficult to get right because it requires strict control over how scripts and resources are loaded, but the real-world web is messy, dynamic, and full of third-party content. Balancing security with usability and maintainability is the core challenge. After all, no one enjoys long delays at airport security but we all appreciate knowing the flight is safe.
In this blog we’re going to discuss:
followed by three examples:
- High-security CSP configuration for maximum protection
- Balanced CSP implementation: security meets usability
- A minimal CSP for those starting from scratch — what to do if you’re implementing CSP for the first time
CSP directives explained: Understanding the building blocks
Directives are the building blocks of a CSP. Each directive specifies what kind of content is allowed from which sources, giving you granular control over your web application security. Here is an overview of the most important CSP directives and their security benefits:
Directive | Purpose |
---|---|
'none' |
Blocks all resources of the specified type. For example, default-src 'none' means nothing is allowed to load unless explicitly permitted by other directives. |
'self' |
Only allows resources to be loaded from the same origin (your own domain). |
data: |
Permits resources to be loaded from data URIs (e.g., inline images or fonts). Use with caution, as it can introduce security risks. |
https: |
Allows resources to be loaded from any HTTPS source, respectively. For example, img-src https: allows images from any HTTPS site. |
nonce-... |
Allows scripts or styles with a matching cryptographic nonce attribute. This is a random value generated per request, enabling safe inline scripts/styles while blocking others. |
sha256-... |
Permits specific inline scripts or styles that match a given SHA-256 hash. This is useful for allowing only known, trusted inline code. |
'unsafe-inline' |
Allows any inline scripts or styles. This is generally discouraged, as it can make your site vulnerable to XSS attacks. |
'unsafe-eval' |
Permits the use of JavaScript's eval() and similar methods. This is also discouraged for security reasons. |
'strict-dynamic' |
When used with a nonce or hash, allows dynamically loaded scripts to inherit trust, but blocks all other scripts unless explicitly whitelisted. |
report-to |
Specifies where the browser should send CSP violation reports. |
object-src 'none' |
Blocks plugins like Flash or Java applets (legacy security best practice). |
frame-ancestors 'none' |
Prevents your site from being embedded in frames or iframes, protecting against clickjacking. |
These rules can be combined to create a policy that balances web application security and usability for your website.
Example 1: High-security CSP configuration for maximum protection
If web application security is your main priority, then this example CSP configuration will significantly reduce the attack vectors on your website and provide maximum XSS prevention.
Directive | Purpose |
---|---|
default-src 'none' |
Denies all resource loading by default unless explicitly allowed by other directives. |
script-src 'nonce-abc123' |
Allows only scripts with the specified nonce to execute, blocking all other scripts. |
style-src 'nonce-abc123' |
Allows only styles with the specified nonce to be applied. |
img-src 'self' |
Allows images to load only from the same origin. |
object-src 'none' |
Disables plugins like Flash or Java applets. |
connect-src 'self' |
Restricts fetch/XHR/WebSocket connections to the same origin. |
base-uri 'none' |
Disallows the use of the <base> tag, preventing manipulation of relative URL resolution. |
require-trusted-types-for 'script' |
Enforces the use of Trusted Types for script injection points to mitigate DOM (Document Object Model) XSS. |
trusted-types 'default' |
Specifies a Trusted Types policy named default. Note you will have to implement this policy within your JavaScript, like this:
|
report-to csp-endpoint; |
Sends CSP violation reports to the specified reporting endpoint. |
Implementing this robust CSP is challenging due to several factors. This CSP policy won’t enable you to use many third-party scripts and styles, such as Google Analytics or Stripe, unless they are explicitly whitelisted or self-hosted. You can use them if they are hosted on your own origin and served with the correct nonce applied by your server, but this is particularly challenging for static content sites.
Integrating Trusted Types — an additional security layer to prevent XSS — requires significant refactoring of code that manipulates the DOM. This can be particularly problematic for legacy or third-party libraries that aren’t designed to be Trusted Types-compliant.
Summary: While this approach may greatly improve security, it also limits the third-party libraries you can use and demands that any new code be compatible with Trusted Types — which is often a non-trivial task. Whilst setting up your site to work with all these policies, it may feel like you’re responsible for a very long queue at the airport security, but once you’re done you’ll know that all the passengers will be thoroughly checked for any risks.
Example 2: Balanced CSP implementation: security meets usability
While the CSP configuration above offers strong web security, the constraints can significantly limit your ability to build and maintain a functional website. This example CSP implementation considers the trade offs between security and usability.
Directive | Purpose |
---|---|
default-src 'self'; |
Sets the default policy for all resource types to only allow same-origin. |
script-src https://static.cloudflareinsights.com 'nonce-12345678' 'strict-dynamic'; |
Allows scripts from Cloudflare Insights, enables nonce-based script execution, and allows dynamic loading based on trusted scripts. |
style-src https://fonts.googleapis.com 'nonce-12345678'; |
Allows styles from Google Fonts, and inline styles with a matching nonce. |
object-src 'none'; |
Blocks all <object> , <embed> , and <applet> elements (for legacy plugin security). |
img-src data: https:; |
Allows images from data URIs and any HTTPS source. |
frame-src data: https://js.stripe.com; |
Allows embedding frames from data URIs and Stripe. |
font-src https://fonts.gstatic.com; |
Allows fonts to load from Google Fonts CDN. |
connect-src https://cloudflareinsights.com; |
Allows XHR/WebSocket/fetch connections to Cloudflare. |
frame-ancestors 'none' https://test.com; |
Prevents the site from being embedded except by test.com . |
base-uri 'self'; |
Restricts the <base> tag to same-origin only. |
form-action 'self'; |
Only allows forms to be submitted to the same origin. |
upgrade-insecure-requests; |
Automatically upgrades all HTTP requests to HTTPS. |
script-src-attr 'none'; |
Disallows inline scripts via HTML attributes like onclick . This is present to override the given default-src . |
report-to csp-endpoint; |
Sends CSP violation reports to the specified reporting endpoint. |
This example enforces strict controls on scripts, styles, images, fonts, and connections, only permitting sources from the same origin or well-known trusted domains like Cloudflare and Google Fonts. Inline scripts and unsafe attributes are blocked, while all HTTP requests are upgraded to HTTPS. Additionally, the policy includes reporting mechanisms to help diagnose security issues.
Summary: This CSP setup strengthens security but demands careful handling of third-party content and Trusted Types compliance, limiting flexibility and increasing development effort. The queue for the aircraft security gate is starting to look a bit more manageable, but you’re still primarily focused on security.
Example 3: Minimal CSP setup for beginners: your first CSP
If you don’t have a CSP policy and want to implement CSP for the first time with minimal risk of breaking your site, here’s a sensible beginner-friendly CSP configuration example.
Directive | Description |
---|---|
default-src 'self' |
Basic fallback: only allow content from your own domain. |
script-src 'self' |
Allows scripts from the same origin ('self' ).
Note: If you include 'unsafe-eval' , your CSP loses much of its effectiveness — especially against script injection attacks. If you're implementing a CSP for security, it’s best to avoid this directive unless absolutely necessary and only as a temporary workaround. |
style-src 'self' 'unsafe-inline' |
Allows inline styles, avoids breaking visual layout. |
img-src https: data: blob: |
Allows images from anywhere (common for marketing/content-heavy sites). |
connect-src * |
Allows XHR, fetch, and WebSocket connections to any endpoint. |
font-src * |
Permits fonts from any source (e.g., Google Fonts). |
object-src 'none' |
Blocks Flash, Java applets, etc. (outdated but still best practice). |
frame-ancestors 'none' |
Prevents clickjacking by blocking embedding. |
upgrade-insecure-requests |
Forces all HTTP resources to be loaded via HTTPS automatically. |
report-to csp-endpoint; |
Sends CSP violation reports to the specified reporting endpoint. |
This Content Security Policy allows resources mainly from your own domain but includes some relaxed rules like permitting inline scripts, images from any source, and connections to all endpoints to avoid breaking functionality. It also blocks outdated plugins, prevents clickjacking, and forces HTTPS connections.
Summary: While these fallbacks create some security gaps, they still offer important protections without disrupting site behaviour. So, if you don’t have any airport security at all and you want to add something, this would be a good starting point.
Conclusion: Mastering Content Security Policy implementation
Balancing your CSP policy between security and usability requires careful consideration—like managing airport security: too strict, and you frustrate passengers; too lax, and you risk letting threats through. To help with this challenge, Hexiosec ASM uncovers all the CSP policies in use across your organisation, analyses the security of your CSP policies, and offers actionable remediation advice, so all the users of your organisation’s websites can be safe – and enjoy their flight.