Content Security Policy


Introduction

Content Security Policy (CSP) is a browser security mechanism that mitigates Cross-Site Scripting (XSS), data injection, and clickjacking attacks. By defining a whitelist of trusted content sources, CSP prevents the browser from executing malicious scripts or loading unauthorized resources.

CSP Directives

CSP uses HTTP headers with directives that control specific resource types.




Content-Security-Policy:


default-src 'self';


script-src 'self' https://cdn.trusted.com;


style-src 'self' 'unsafe-inline';


img-src 'self' data: https://*.cloudfront.net;


connect-src 'self' https://api.example.com;


font-src 'self' https://fonts.gstatic.com;


frame-src 'none';


object-src 'none';


base-uri 'self';


form-action 'self';





Key directives:

| Directive | Controls | Example | |-----------|----------|---------| | `default-src` | Fallback for all resource types | `default-src 'self'` | | `script-src` | JavaScript sources | `script-src 'self' https://cdn.example.com` | | `style-src` | CSS sources | `style-src 'self' 'unsafe-inline'` | | `img-src` | Image sources | `img-src 'self' data:` | | `connect-src` | XMLHttpRequest, fetch, WebSocket | `connect-src 'self'` | | `frame-ancestors` | Parent page framing | `frame-ancestors 'none'` | | `form-action` | Form submission targets | `form-action 'self'` | | `base-uri` | `` tag URLs | `base-uri 'self'` |

Nonce and Hash Strategies

Using `'unsafe-inline'` for scripts weakens CSP. Nonce and hash strategies provide secure inline script handling.

Nonce-Based CSP

A nonce is a cryptographically random token generated per-request and included in both the CSP header and the script tag.




import secrets


from flask import Flask, render_template




app = Flask(__name__)




@app.route('/')


def index():


nonce = secrets.token_urlsafe(16)


response = make_response(render_template('index.html', nonce=nonce))


response.headers['Content-Security-Policy'] = \


f"script-src 'nonce-{nonce}'; object-src 'none'; base-uri 'self'"


return response




















Hash-Based CSP

For static inline scripts, use a hash of the script content instead of a nonce.




import hashlib


import base64




def generate_script_hash(script_content):


hash_bytes = hashlib.sha256(script_content.encode()).digest()


hash_b64 = base64.b64encode(hash_bytes).decode()


return f"'sha256-{hash_b64}'"




# Generate once, add to CSP


script = "document.getElementById('app').innerHTML = '

Hello

';"


script_hash = generate_script_hash(script)




# CSP: script-src 'sha256-abc123...'








Content-Security-Policy:


script-src 'sha256-abc123def456...';


object-src 'none';





CSP Reporting

CSP violations can be reported to an endpoint for monitoring and debugging.




Content-Security-Policy:


default-src 'self';


script-src 'self';


report-uri /csp-violation-report;


report-to csp-endpoint;








{


"csp-report": {


"document-uri": "https://example.com/page",


"referrer": "",


"blocked-uri": "https://evil.com/script.js",


"violated-directive": "script-src 'self'",


"effective-directive": "script-src",


"original-policy": "default-src 'self'; script-src 'self'",


"source-file": "https://example.com/page",


"line-number": 42,


"column-number": 10


}


}








# Flask CSP report collector


@app.route('/csp-violation-report', methods=['POST'])


def csp_report():


report = request.get_json(force=True)




blocked = report['csp-report']['blocked-uri']


directive = report['csp-report']['violated-directive']


page = report['csp-report']['document-uri']




# Log to security monitoring


security_logger.warning(


f"CSP violation on {page}: {directive} blocked {blocked}"


)




# Alert if critical pattern


if 'exfiltration' in blocked.lower():


security_logger.critical(f"Potential data exfiltration: {blocked}")




return '', 204





Strict CSP Migration

Migrating from allowlist CSP to strict CSP provides better security.




# Allowlist CSP (vulnerable to CDN-based bypass)


Content-Security-Policy:


script-src 'self' https://ajax.googleapis.com https://cdnjs.cloudflare.com;




# Strict CSP (nonce-based, resistant to bypass)


Content-Security-Policy:


script-src 'nonce-random123' 'strict-dynamic' https: 'unsafe-inline';


object-src 'none';


base-uri 'none';





The `'strict-dynamic'` keyword propagates trust from nonced scripts to their dynamically loaded children, enabling modern SPA frameworks while maintaining security.

Deployment Strategy




# Step 1: Deploy in report-only mode


Content-Security-Policy-Report-Only:


default-src 'self';


script-src 'nonce-abc123';


report-uri /csp-reports;




# Step 2: Monitor reports for 2-4 weeks


# Step 3: Fix violations identified in reports


# Step 4: Switch to enforcement mode


Content-Security-Policy:


default-src 'self';


script-src 'nonce-abc123';


report-uri /csp-reports;




# Step 5: Gradually remove report-uri when confident





Conclusion

CSP is one of the most powerful defense-in-depth mechanisms against XSS. Use nonce-based strict CSP rather than allowlist-based policies, deploy in report-only mode initially to identify violations, always set `object-src 'none'` and `base-uri 'self'` as hardening measures, and monitor reports continuously for policy violations and potential attacks.