""" Security configuration for thrillwiki project. This module configures security headers and settings to protect against common web vulnerabilities including XSS, clickjacking, MIME sniffing, and more. Uses python-decouple for consistent environment variable management. Why python-decouple? - Already used in base.py for consistency - Simpler API than django-environ - Sufficient for our configuration needs - Better separation of config from code """ from decouple import config # ============================================================================= # Cloudflare Turnstile Configuration # ============================================================================= # Turnstile is Cloudflare's CAPTCHA alternative for bot protection # Get keys from: https://dash.cloudflare.com/?to=/:account/turnstile TURNSTILE_SITE_KEY = config("TURNSTILE_SITE_KEY", default="") TURNSTILE_SECRET_KEY = config("TURNSTILE_SECRET_KEY", default="") TURNSTILE_VERIFY_URL = config( "TURNSTILE_VERIFY_URL", default="https://challenges.cloudflare.com/turnstile/v0/siteverify", ) # ============================================================================= # Security Headers Configuration # ============================================================================= # X-XSS-Protection: Enables browser's built-in XSS filter # Note: Modern browsers are deprecating this in favor of CSP, but it's still # useful for older browsers SECURE_BROWSER_XSS_FILTER = config( "SECURE_BROWSER_XSS_FILTER", default=True, cast=bool ) # X-Content-Type-Options: Prevents MIME type sniffing attacks # When True, adds "X-Content-Type-Options: nosniff" header SECURE_CONTENT_TYPE_NOSNIFF = config( "SECURE_CONTENT_TYPE_NOSNIFF", default=True, cast=bool ) # X-Frame-Options: Protects against clickjacking attacks # DENY = Never allow framing (most secure) # SAMEORIGIN = Only allow framing from same origin X_FRAME_OPTIONS = config("X_FRAME_OPTIONS", default="DENY") # Referrer-Policy: Controls how much referrer information is sent # strict-origin-when-cross-origin = Send full URL for same-origin, # only origin for cross-origin, nothing for downgrade SECURE_REFERRER_POLICY = config( "SECURE_REFERRER_POLICY", default="strict-origin-when-cross-origin" ) # Cross-Origin-Opener-Policy: Prevents cross-origin attacks via window references # same-origin = Document can only be accessed by windows from same origin SECURE_CROSS_ORIGIN_OPENER_POLICY = config( "SECURE_CROSS_ORIGIN_OPENER_POLICY", default="same-origin" ) # ============================================================================= # HSTS (HTTP Strict Transport Security) Configuration # ============================================================================= # Include subdomains in HSTS policy SECURE_HSTS_INCLUDE_SUBDOMAINS = config( "SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True, cast=bool ) # HSTS max-age in seconds (31536000 = 1 year, recommended minimum) SECURE_HSTS_SECONDS = config("SECURE_HSTS_SECONDS", default=31536000, cast=int) # HSTS preload: Allow inclusion in browser preload lists # Only enable after confirming HTTPS works properly for all subdomains SECURE_HSTS_PRELOAD = config("SECURE_HSTS_PRELOAD", default=False, cast=bool) # URLs exempt from SSL redirect (e.g., health checks) # Format: comma-separated list of URL patterns SECURE_REDIRECT_EXEMPT = config( "SECURE_REDIRECT_EXEMPT", default="", cast=lambda v: [s.strip() for s in v.split(",") if s.strip()] ) # Redirect all HTTP requests to HTTPS SECURE_SSL_REDIRECT = config("SECURE_SSL_REDIRECT", default=False, cast=bool) # Header used by proxy to indicate HTTPS # Common values: ('HTTP_X_FORWARDED_PROTO', 'https') _proxy_ssl_header = config("SECURE_PROXY_SSL_HEADER", default="") SECURE_PROXY_SSL_HEADER = ( tuple(_proxy_ssl_header.split(",")) if _proxy_ssl_header else None ) # ============================================================================= # Session Cookie Security # ============================================================================= # Only send session cookie over HTTPS SESSION_COOKIE_SECURE = config("SESSION_COOKIE_SECURE", default=False, cast=bool) # Prevent JavaScript access to session cookie (mitigates XSS) SESSION_COOKIE_HTTPONLY = config("SESSION_COOKIE_HTTPONLY", default=True, cast=bool) # SameSite attribute: Protects against CSRF attacks # Strict = Cookie only sent for same-site requests (most secure) # Lax = Cookie sent for same-site and top-level navigations (default) SESSION_COOKIE_SAMESITE = config("SESSION_COOKIE_SAMESITE", default="Lax") # ============================================================================= # CSRF Cookie Security # ============================================================================= # Only send CSRF cookie over HTTPS CSRF_COOKIE_SECURE = config("CSRF_COOKIE_SECURE", default=False, cast=bool) # Prevent JavaScript access to CSRF cookie # Note: Set to False if you need to read the token via JavaScript for AJAX CSRF_COOKIE_HTTPONLY = config("CSRF_COOKIE_HTTPONLY", default=True, cast=bool) # SameSite attribute for CSRF cookie CSRF_COOKIE_SAMESITE = config("CSRF_COOKIE_SAMESITE", default="Lax") # ============================================================================= # Authentication Backends # ============================================================================= # Order matters: Django tries each backend in order until one succeeds AUTHENTICATION_BACKENDS = [ "django.contrib.auth.backends.ModelBackend", "allauth.account.auth_backends.AuthenticationBackend", ] # ============================================================================= # Password Validators # ============================================================================= # Django's built-in password validators for security AUTH_PASSWORD_VALIDATORS = [ { "NAME": ( "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" ), }, { "NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": { "min_length": config("PASSWORD_MIN_LENGTH", default=8, cast=int), }, }, { "NAME": "django.contrib.auth.password_validation.CommonPasswordValidator", }, { "NAME": "django.contrib.auth.password_validation.NumericPasswordValidator", }, ] # ============================================================================= # Permissions Policy (Feature Policy successor) # ============================================================================= # Controls which browser features can be used PERMISSIONS_POLICY = { "accelerometer": [], "ambient-light-sensor": [], "autoplay": [], "camera": [], "display-capture": [], "document-domain": [], "encrypted-media": [], "fullscreen": ["self"], "geolocation": ["self"], # Required for map features "gyroscope": [], "interest-cohort": [], # Block FLoC "magnetometer": [], "microphone": [], "midi": [], "payment": [], "picture-in-picture": [], "publickey-credentials-get": [], "screen-wake-lock": [], "sync-xhr": [], "usb": [], "web-share": ["self"], "xr-spatial-tracking": [], }