Add secret management guide, client-side performance monitoring, and search accessibility enhancements

- Introduced a comprehensive Secret Management Guide detailing best practices, secret classification, development setup, production management, rotation procedures, and emergency protocols.
- Implemented a client-side performance monitoring script to track various metrics including page load performance, paint metrics, layout shifts, and memory usage.
- Enhanced search accessibility with keyboard navigation support for search results, ensuring compliance with WCAG standards and improving user experience.
This commit is contained in:
pacnpal
2025-12-23 16:41:42 -05:00
parent ae31e889d7
commit edcd8f2076
155 changed files with 22046 additions and 4645 deletions

View File

@@ -3,16 +3,27 @@ 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
"""
import environ
from decouple import config
env = environ.Env()
# =============================================================================
# Cloudflare Turnstile Configuration
# =============================================================================
# Turnstile is Cloudflare's CAPTCHA alternative for bot protection
# Get keys from: https://dash.cloudflare.com/?to=/:account/turnstile
# Cloudflare Turnstile settings
TURNSTILE_SITE_KEY = env("TURNSTILE_SITE_KEY", default="")
TURNSTILE_SECRET_KEY = env("TURNSTILE_SECRET_KEY", default="")
TURNSTILE_VERIFY_URL = env(
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",
)
@@ -24,27 +35,31 @@ TURNSTILE_VERIFY_URL = env(
# 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 = env.bool("SECURE_BROWSER_XSS_FILTER", default=True)
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 = env.bool("SECURE_CONTENT_TYPE_NOSNIFF", default=True)
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 = env("X_FRAME_OPTIONS", default="DENY")
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 = env(
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 = env(
SECURE_CROSS_ORIGIN_OPENER_POLICY = config(
"SECURE_CROSS_ORIGIN_OPENER_POLICY", default="same-origin"
)
@@ -53,79 +68,104 @@ SECURE_CROSS_ORIGIN_OPENER_POLICY = env(
# =============================================================================
# Include subdomains in HSTS policy
SECURE_HSTS_INCLUDE_SUBDOMAINS = env.bool(
"SECURE_HSTS_INCLUDE_SUBDOMAINS", default=True
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 = env.int("SECURE_HSTS_SECONDS", default=31536000)
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 = env.bool("SECURE_HSTS_PRELOAD", default=False)
SECURE_HSTS_PRELOAD = config("SECURE_HSTS_PRELOAD", default=False, cast=bool)
# URLs exempt from SSL redirect (e.g., health checks)
SECURE_REDIRECT_EXEMPT = env.list("SECURE_REDIRECT_EXEMPT", default=[])
# 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 = env.bool("SECURE_SSL_REDIRECT", default=False)
SECURE_SSL_REDIRECT = config("SECURE_SSL_REDIRECT", default=False, cast=bool)
# Header used by proxy to indicate HTTPS (e.g., ('HTTP_X_FORWARDED_PROTO', 'https'))
SECURE_PROXY_SSL_HEADER = env.tuple("SECURE_PROXY_SSL_HEADER", default=None)
# 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 = env.bool("SESSION_COOKIE_SECURE", default=False)
SESSION_COOKIE_SECURE = config("SESSION_COOKIE_SECURE", default=False, cast=bool)
# Prevent JavaScript access to session cookie (mitigates XSS)
SESSION_COOKIE_HTTPONLY = env.bool("SESSION_COOKIE_HTTPONLY", default=True)
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 = env("SESSION_COOKIE_SAMESITE", default="Lax")
SESSION_COOKIE_SAMESITE = config("SESSION_COOKIE_SAMESITE", default="Lax")
# =============================================================================
# CSRF Cookie Security
# =============================================================================
# Only send CSRF cookie over HTTPS
CSRF_COOKIE_SECURE = env.bool("CSRF_COOKIE_SECURE", default=False)
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 = env.bool("CSRF_COOKIE_HTTPONLY", default=True)
CSRF_COOKIE_HTTPONLY = config("CSRF_COOKIE_HTTPONLY", default=True, cast=bool)
# SameSite attribute for CSRF cookie
CSRF_COOKIE_SAMESITE = env("CSRF_COOKIE_SAMESITE", default="Lax")
CSRF_COOKIE_SAMESITE = config("CSRF_COOKIE_SAMESITE", default="Lax")
# =============================================================================
# File Upload Security
# Authentication Backends
# =============================================================================
# Order matters: Django tries each backend in order until one succeeds
# Maximum size (in bytes) of file to upload into memory (2.5MB)
FILE_UPLOAD_MAX_MEMORY_SIZE = env.int(
"FILE_UPLOAD_MAX_MEMORY_SIZE", default=2621440
)
AUTHENTICATION_BACKENDS = [
"django.contrib.auth.backends.ModelBackend",
"allauth.account.auth_backends.AuthenticationBackend",
]
# Maximum size (in bytes) of request data (10MB)
DATA_UPLOAD_MAX_MEMORY_SIZE = env.int(
"DATA_UPLOAD_MAX_MEMORY_SIZE", default=10485760
)
# =============================================================================
# Password Validators
# =============================================================================
# Django's built-in password validators for security
# File upload permissions (0o644 = rw-r--r--)
FILE_UPLOAD_PERMISSIONS = 0o644
# Directory permissions for uploaded files (0o755 = rwxr-xr-x)
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755
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
# =============================================================================
# Controls which browser features can be used
PERMISSIONS_POLICY = {
"accelerometer": [],
"ambient-light-sensor": [],