From e95369ffbf7cf5c0cd984d41ad4846959713bfc6 Mon Sep 17 00:00:00 2001 From: Keith Smith Date: Fri, 26 Dec 2025 17:40:34 -0700 Subject: [PATCH] Add Content Security Policy (CSP) headers MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements CSP to prevent XSS attacks and complete Mozilla Observatory security requirements. Changes: - Added django-csp>=3.8 to requirements.txt - Added CSPMiddleware to middleware stack - Configured CSP directives in selfhosted.py: - default-src 'self' (only load resources from same origin) - script-src/style-src allow 'unsafe-inline' (needed for Django admin) - img-src allows https: and data: URIs - frame-ancestors 'none' (prevent clickjacking) - form-action 'self' (prevent form hijacking) This policy balances security with Django admin functionality. After deployment, Mozilla Observatory should show all green checks. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 --- config/settings/base.py | 1 + config/settings/selfhosted.py | 13 +++++++++++++ requirements.txt | 3 +++ 3 files changed, 17 insertions(+) diff --git a/config/settings/base.py b/config/settings/base.py index 1725362..109b0ea 100644 --- a/config/settings/base.py +++ b/config/settings/base.py @@ -36,6 +36,7 @@ INSTALLED_APPS = [ MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', + 'csp.middleware.CSPMiddleware', # Content Security Policy 'whitenoise.middleware.WhiteNoiseMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', diff --git a/config/settings/selfhosted.py b/config/settings/selfhosted.py index 6a7ad25..4ab8126 100644 --- a/config/settings/selfhosted.py +++ b/config/settings/selfhosted.py @@ -135,6 +135,19 @@ SECURE_BROWSER_XSS_FILTER = True SECURE_CONTENT_TYPE_NOSNIFF = True X_FRAME_OPTIONS = 'DENY' # Prevent clickjacking +# Content Security Policy (CSP) +# Helps prevent XSS attacks by controlling what resources can load +# This policy allows Django admin to function while providing good security +CSP_DEFAULT_SRC = ("'self'",) +CSP_SCRIPT_SRC = ("'self'", "'unsafe-inline'") # unsafe-inline needed for Django admin +CSP_STYLE_SRC = ("'self'", "'unsafe-inline'") # unsafe-inline needed for Django admin +CSP_IMG_SRC = ("'self'", "data:", "https:") # Allow images from HTTPS sources +CSP_FONT_SRC = ("'self'", "data:") +CSP_CONNECT_SRC = ("'self'",) +CSP_FRAME_ANCESTORS = ("'none'",) # Equivalent to X-Frame-Options: DENY +CSP_BASE_URI = ("'self'",) +CSP_FORM_ACTION = ("'self'",) + # Static files STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage' diff --git a/requirements.txt b/requirements.txt index 6b17664..3545eec 100644 --- a/requirements.txt +++ b/requirements.txt @@ -28,3 +28,6 @@ Pillow>=10.0.0 # Production gunicorn>=21.0.0 whitenoise>=6.6.0 + +# Security +django-csp>=3.8