XSS Defense: Content Security Policy
Content Security Policy (CSP) is an HTTP response header that tells the browser which sources of content are permitted to load and execute. It does not prevent injection; it limits what injected scripts can do and where they can run from.
The script-src Directive
The script-src directive controls which sources of JavaScript the browser executes. A policy of:
Content-Security-Policy: script-src 'self'
permits scripts loaded from the same origin and blocks scripts from any other origin. Inline scripts (code written directly in <script> tags in the HTML) are blocked by default when script-src is specified without 'unsafe-inline'. This is significant: most XSS payloads are inline scripts. Blocking inline execution eliminates the most common injection vectors.
Event handler attributes (onerror, onclick, etc.) are also considered inline and blocked by the same rule.
Nonces and Hashes
Applications that require inline scripts can use nonces or hashes instead of 'unsafe-inline'.
A nonce is a per-request random value included in both the CSP header and the nonce attribute of legitimate script elements:
Content-Security-Policy: script-src 'nonce-r4nd0m'
<script nonce="r4nd0m">/* legitimate code */</script>
The browser executes only scripts whose nonce attribute matches the value in the policy. An injected <script> element without the correct nonce is blocked. The nonce must be unguessable and generated fresh for each response. If the nonce is static or predictable, it provides no protection.
A hash specifies the SHA-256 (or SHA-384, SHA-512) hash of the exact script content. The browser executes the script only if its content matches the hash. This works for static inline scripts but not for dynamically generated ones.
Limiting Exfiltration: connect-src and img-src
Even if a script executes, CSP can restrict where it can send data. The connect-src directive controls which URLs fetch() and XMLHttpRequest can contact. The img-src directive controls image sources, including 1x1 tracking pixels used for exfiltration. Restricting both to 'self' prevents data from being sent to attacker-controlled external servers through those mechanisms.
Limits of CSP
CSP is a defense-in-depth measure, not a complete XSS prevention mechanism. It has several limitations:
Allowlisted script sources: if script-src includes a CDN or third-party domain that serves user-controlled content or JSONP endpoints, an attacker can load script from that domain.
'unsafe-inline' and 'unsafe-eval': policies that include these keywords to maintain compatibility with legacy code eliminate the protections they are meant to provide.
Nonce reuse: static nonces in server-rendered templates are equivalent to no nonce at all.
DOM XSS: CSP does not prevent DOM-based XSS where JavaScript on the page itself reads attacker-controlled input and writes it to a sink. The script executing is already on the allowlisted origin.
CSP is most useful as a secondary layer after output encoding. It reduces impact when encoding fails but does not substitute for encoding.