Content Security Policy
Definition
Content Security Policy (CSP) is a browser security control that tells the browser which sources and execution patterns are allowed for scripts, styles, images, frames, connections, and other resource types.
Why it matters
CSP is defense-in-depth for XSS and content injection, not a replacement for output encoding or safe templating. A strong policy can reduce exploitability when an injection bug exists, while a weak policy can create false confidence.
How it works
CSP controls 5 browser decisions:
- Script execution. Which scripts may load or execute.
- Resource loading. Which origins may provide images, styles, fonts, media, and frames.
- Network connections. Which endpoints JavaScript may connect to.
- Embedding. Who can frame the page and what the page can frame.
- Reporting. Where violations are reported.
Example header:
Content-Security-Policy: default-src 'self'; script-src 'self' 'nonce-random'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'
The bug is not absence of CSP alone. The bug is relying on CSP while allowing unsafe sources or inline execution that defeats the policy.
Techniques / patterns
Attackers test:
script-srcweaknesses:unsafe-inline, wildcards, broad CDNs, JSONP, upload origins- missing nonces or reusable nonces
object-src,base-uri, andframe-ancestorsomissions- bypassable trusted domains
- DOM XSS sinks under policy constraints
- report-only policies mistaken for enforcement
Variants and bypasses
CSP failure appears in 6 forms.
1. No policy
The browser receives no meaningful execution restrictions.
2. Report-only policy
Violations are reported but not blocked.
3. Unsafe inline execution
unsafe-inline, unsafe hashes, or nonce misuse allows injected scripts.
4. Overbroad trusted sources
Wildcards or broad CDNs allow attacker-controlled content from trusted origins.
5. Missing structural directives
Missing base-uri, object-src, or frame-ancestors leaves bypass paths open.
6. Policy drift
The policy grows permissive over time to support new features.
Impact
Ordered roughly by severity:
- XSS exploitability reduction or failure. Strong CSP can block injected script execution.
- Clickjacking and framing control.
frame-ancestorsprotects sensitive pages. - Data exfiltration reduction.
connect-srclimits where scripts can send data. - Content injection containment. Resource restrictions reduce malicious loading.
- Telemetry. Reports reveal attempted violations and policy gaps.
Detection and defense
Ordered by effectiveness:
-
Fix injection at the source first. Output encoding, safe templating, sanitization, and DOM safety are primary XSS defenses.
-
Use nonce-based or hash-based
script-srcwithout unsafe inline execution. This is the most meaningful CSP control for modern XSS reduction. -
Set restrictive defaults and structural directives.
default-src 'self',object-src 'none',base-uri 'none', andframe-ancestorsreduce bypass space. -
Keep trusted origins narrow. Every allowed origin should be owned, necessary, and unable to host attacker-controlled scripts.
-
Deploy with reporting and review violations. Report-only is useful during rollout, but final protection requires enforcement.
What does not work as a primary defense
- CSP instead of output encoding. CSP is defense-in-depth, not the primary XSS fix.
unsafe-inline. It usually defeats the point of script restrictions.- Broad wildcards.
*.cdn.examplecan include attacker-controlled paths. - Report-only mode. It observes but does not block.
Practical labs
Use owned apps.
Inspect policy
curl -I https://app.example.test/ | rg -i "content-security-policy"
Check whether the policy is enforced or report-only.
Review risky directives
curl -I https://app.example.test/ | rg -i "unsafe-inline|unsafe-eval|\\*|data:"
Each risky source should have a justification.
Test frame protection
curl -I https://app.example.test/account | rg -i "frame-ancestors|x-frame-options"
Sensitive pages should not be broadly frameable.
Practical examples
- A site has XSS but a strong nonce-based CSP blocks script execution.
- A policy uses
unsafe-inline, making it ineffective against many XSS bugs. - A trusted CDN path allows user-uploaded JavaScript.
frame-ancestorsis missing on account settings.- CSP report-only logs violations but does not block attacks.
Related notes
Suggested future atomic notes
- nonce-based-csp
- csp-bypasses
- trusted-types
- frame-ancestors
- csp-reporting
References
- Foundational: MDN Content Security Policy — https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP
- Testing / Lab: PortSwigger CSP — https://portswigger.net/web-security/cross-site-scripting/content-security-policy
- Foundational: OWASP Content Security Policy Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Content_Security_Policy_Cheat_Sheet.html