# Content Security Policy (CSP) ## Rule Define strict CSP to prevent XSS. Start restrictive, loosen only as needed. Never use `unsafe-inline` for scripts. **Source:** [MDN Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) ## CSP Directives | Directive | Controls | |-----------|----------| | `default-src` | Fallback for all resource types | | `script-src` | JavaScript sources | | `style-src` | CSS sources | | `img-src` | Image sources | | `connect-src` | XHR, fetch, WebSocket | | `frame-src` | iframe sources | | `frame-ancestors` | Who can embed this page | | `form-action` | Form submission targets | | `base-uri` | `` tag restrictions | ## Correct Pattern ```python # Strict CSP with nonces (recommended) import secrets def generate_csp_nonce() -> str: return secrets.token_urlsafe(16) def get_csp_header(nonce: str) -> str: """Generate strict CSP header.""" return "; ".join([ "default-src 'self'", f"script-src 'nonce-{nonce}' 'strict-dynamic'", "style-src 'self' 'nonce-{nonce}'", "img-src 'self' data: https:", "font-src 'self'", "connect-src 'self' https://api.example.com", "frame-ancestors 'none'", "form-action 'self'", "base-uri 'self'", "upgrade-insecure-requests", ]) @app.after_request def add_security_headers(response): nonce = generate_csp_nonce() g.csp_nonce = nonce # Make available to templates response.headers["Content-Security-Policy"] = get_csp_header(nonce) return response # In template: # ``` ## Incorrect Pattern ```python # Wrong: unsafe-inline allows XSS csp = "script-src 'self' 'unsafe-inline'" # Wrong: unsafe-eval allows eval() csp = "script-src 'self' 'unsafe-eval'" # Wrong: wildcard allows any source csp = "script-src *" # Wrong: no CSP at all # (missing header) # Wrong: report-only without enforcement # Use for testing, but deploy with enforcement response.headers["Content-Security-Policy-Report-Only"] = csp # ^ Only reports, doesn't block! # Wrong: data: in script-src csp = "script-src 'self' data:" # Attacker can inject: