Content Security Policy
AI-Generated Content
Content Security Policy
For modern web developers, securing an application extends far beyond validating user inputs and using HTTPS. One of the most potent defenses against pervasive client-side attacks is the Content Security Policy, a declarative security layer built into browsers. CSP allows you to dictate exactly which resources your page is permitted to load, transforming your security approach from hoping malicious scripts aren't present to actively forbidding them from executing in the first place. Mastering its configuration is essential for protecting users from cross-site scripting (XSS), data theft, and other injection-based attacks.
What CSP Is and How It Works
At its core, a Content Security Policy is a set of rules communicated from your web server to the user's browser via HTTP response headers. The browser acts as an enforcement agent for these rules. Instead of the traditional model where a browser loads and executes any resource referenced in the HTML, CSP instructs it to load content only from sources you explicitly allow. This drastically reduces the attack surface.
The most common way to deliver a CSP is through the Content-Security-Policy HTTP header. For example, a simple policy might look like:
Content-Security-Policy: default-src 'self';This header tells the browser, "By default, only load resources from my own origin (the same protocol, domain, and port)." If an attacker successfully injects a script tag pointing to evil.com, the browser will refuse to load or execute it because evil.com is not an approved source. This mechanism moves security out of the realm of detecting bad content and into the realm of defining good content.
Core Directives and Source Lists
A CSP is composed of directives, each controlling a specific type of resource. A directive is paired with a source list, which defines the approved origins. Understanding these building blocks is key to crafting an effective policy.
- Script Directives: These are your primary defense against XSS.
-
script-srccontrols JavaScript sources. This includes<script>tags, inline event handlers (onclick), and JavaScript-loaded worker scripts. - Common values include
'self', specific domains likehttps://cdn.example.com, and special keywords like'nonce-'or'sha256-'to allow specific inline scripts. - Style Directives:
style-srcgoverns CSS sources, including<link rel="stylesheet">,<style>tags, and inlinestyleattributes. A restrictive policy prevents attackers from injecting styles that could, for example, create convincing phishing overlays. - Resource Directives: These secure the foundational content of your page.
-
img-srcdefines valid sources for images. -
font-srccontrols web font origins. -
connect-srcrestricts URLs that can be contacted via JavaScript APIs likefetch(),XMLHttpRequest, or WebSockets, crucial for stopping data exfiltration. - Frame and Ancestor Directives: These prevent UI redressing and clickjacking attacks.
-
frame-src(deprecated but still used) or the newerchild-srcandworker-srccontrol nested browsing contexts like<iframe>and web workers. -
frame-ancestorsis critical; it specifies which parents are allowed to embed your page in a<frame>,<iframe>,<object>, or<embed>. Setting it to'none'or'self'stops other sites from framing your content.
A robust policy often starts with default-src 'self';, which acts as a catch-all for directives you haven't explicitly defined, and is then overridden with more specific rules like script-src 'self' https://apis.google.com;.
Report-Only Mode and Gradual Deployment
Implementing a restrictive CSP on a large, existing application can be daunting, as a misconfigured policy will immediately break functionality for users. This is where report-only mode becomes an indispensable tool. Instead of using the Content-Security-Policy header, you deploy the Content-Security-Policy-Report-Only header.
In report-only mode, the browser evaluates your policy and simulates enforcement, but it does not actually block any resources. Any violations are sent as JSON reports to a URI you specify using the report-uri or report-to directives. This allows you to audit your site, discover which resources need to be added to your allowlist, and identify legacy inline code that needs to be refactored, all without impacting the user experience. It is the recommended first step for any CSP deployment, enabling you to fine-tune your policy in a production environment before switching to full enforcement.
Advanced Configuration: Nonces, Hashes, and Strict Policies
To allow legitimate inline scripts or styles (which are otherwise blocked by a CSP that doesn't specify 'unsafe-inline'), you must use secure mechanisms.
- Nonces: A nonce (number used once) is a randomly generated, server-side secret included in the CSP header and as an attribute on the allowed inline script tag. For example:
Header: script-src 'nonce-r4nd0m123' HTML: <script nonce="r4nd0m123">console.log("Allowed!");</script>
The browser will only execute scripts whose nonce attribute value matches the one in the header. The nonce must change with every page load.
- Hashes: You can specify the cryptographic hash (e.g., SHA-256) of an inline script's content in your CSP header. The browser will compute the hash of each inline block and only execute it if it matches an entry in the
script-srcorstyle-srcdirective. This is useful for static, non-changing inline code. -
strict-dynamic: The'strict-dynamic'source expression is a powerful option for modern applications. When placed inscript-src, it signifies that scripts approved by a nonce or hash are trusted and can, in turn, load additional scripts. This allows you to maintain a strict policy for your first-party code while permitting trusted, dynamically-loaded third-party scripts without explicitly listing all possible hostnames.
Common Pitfalls
- Using
'unsafe-inline'as a Permanent Solution: Adding'unsafe-inline'toscript-srccompletely negates CSP's primary protection against XSS. It should only be used as a temporary measure during migration. The long-term goal is to eliminate it by refactoring inline scripts and styles to use nonces or hashes, or moving them to external files. - Overlooking the
default-srcFallback: Forgetting to set adefault-srcdirective means that any resource type you don't explicitly define (e.g.,media-src,object-src) will have no restrictions, creating security gaps. A good practice is to start withdefault-src 'none';and then explicitly enable each resource type you need. - Ignoring Report-Only Mode: Jumping directly to a blocking CSP in production is a recipe for broken functionality and frustrated users. Always use report-only mode first to collect violation reports, understand your application's dependencies, and build an accurate, working policy.
- Misconfiguring
frame-ancestorsfor Embedded Content: If your site is designed to be embedded in other sites (e.g., a widget), settingframe-ancestors 'self'will break that functionality. You need to explicitly list the parent domains allowed to frame your content. Conversely, if embedding isn't required, set it to'none'to definitively block clickjacking.
Summary
- Content Security Policy is a critical HTTP header-based defense that instructs browsers which resources (scripts, styles, images, etc.) a page is allowed to load, moving security from detection to allowlisting.
- The core mechanism involves pairing directives (like
script-src,style-src) with source lists (like'self', specific domains) to create a whitelist of trusted content origins. - CSP is highly effective at mitigating cross-site scripting (XSS), data injection, and clickjacking attacks by preventing the loading and execution of unauthorized resources.
- Always begin deployment using report-only mode (
Content-Security-Policy-Report-Only) to detect violations and refine your policy without disrupting users before enforcing it. - Replace the unsafe
'unsafe-inline'allowance with secure methods like nonces or hashes to permit necessary inline code, and use directives likeframe-ancestorsto control how your site can be embedded by others.