| name | django-security |
| description | Django security best practices. Covers CSRF, XSS, SQL injection prevention,
authentication hardening, Content Security Policy, secrets management,
and Django-specific security middleware configuration.
|
| license | MIT |
| compatibility | Claude Code 2.1+ |
| metadata | {"author":"peopleforrester","version":"1.0.0","tags":["security","django","framework","python"]} |
Django Security Patterns
Security hardening for Django 5.x applications.
Settings Hardening
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = "DENY"
ALLOWED_HOSTS = [".example.com"]
import os
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
CSRF Protection
from rest_framework.authentication import SessionAuthentication
class CsrfExemptSessionAuth(SessionAuthentication):
"""Only use for webhook endpoints with signature verification."""
def enforce_csrf(self, request):
return
import hmac
import hashlib
def verify_webhook_signature(payload: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature)
Input Validation
from django.core.validators import RegexValidator
from django.db import models
class User(models.Model):
username = models.CharField(
max_length=30,
validators=[RegexValidator(r"^[a-zA-Z0-9_]+$", "Alphanumeric and underscores only")],
)
email = models.EmailField(unique=True)
from rest_framework import serializers
class UserInputSerializer(serializers.Serializer):
username = serializers.RegexField(r"^[a-zA-Z0-9_]+$", max_length=30)
email = serializers.EmailField()
def validate_username(self, value):
if value.lower() in {"admin", "root", "system"}:
raise serializers.ValidationError("Reserved username.")
return value
SQL Injection Prevention
users = User.objects.filter(name=user_input)
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT * FROM users WHERE name = %s", [user_input])
Authentication Hardening
from django.contrib.auth.backends import ModelBackend
from django.core.cache import cache
class RateLimitedAuthBackend(ModelBackend):
MAX_ATTEMPTS = 5
LOCKOUT_SECONDS = 300
def authenticate(self, request, username=None, password=None, **kwargs):
cache_key = f"login_attempts:{username}"
attempts = cache.get(cache_key, 0)
if attempts >= self.MAX_ATTEMPTS:
return None
user = super().authenticate(request, username=username, password=password, **kwargs)
if user is None:
cache.set(cache_key, attempts + 1, self.LOCKOUT_SECONDS)
else:
cache.delete(cache_key)
return user
AUTH_PASSWORD_VALIDATORS = [
{"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator"},
{"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator", "OPTIONS": {"min_length": 12}},
{"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"},
{"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"},
]
Content Security Policy
MIDDLEWARE = [
"csp.middleware.CSPMiddleware",
]
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'",)
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
CSP_IMG_SRC = ("'self'", "data:", "https:")
CSP_FONT_SRC = ("'self'",)
CSP_CONNECT_SRC = ("'self'",)
CSP_FRAME_SRC = ("'none'",)
CSP_OBJECT_SRC = ("'none'",)
XSS Prevention
import bleach
ALLOWED_TAGS = ["p", "b", "i", "a", "ul", "ol", "li", "br"]
ALLOWED_ATTRS = {"a": ["href", "title"]}
def sanitize_html(html_input: str) -> str:
return bleach.clean(html_input, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRS, strip=True)
Secrets Management
import os
from pathlib import Path
DATABASE_URL = os.environ["DATABASE_URL"]
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
API_KEY = os.environ["EXTERNAL_API_KEY"]
import environ
env = environ.Env()
environ.Env.read_env(Path(__file__).resolve().parent / ".env")
SECRET_KEY = env("DJANGO_SECRET_KEY")
DEBUG = env.bool("DEBUG", default=False)
DATABASE_URL = env.db("DATABASE_URL")
File Upload Security
import os
import uuid
from django.core.exceptions import ValidationError
def validate_file_extension(value):
ext = os.path.splitext(value.name)[1].lower()
allowed = {".jpg", ".jpeg", ".png", ".gif", ".pdf"}
if ext not in allowed:
raise ValidationError(f"Unsupported file extension: {ext}")
def upload_to(instance, filename):
"""Generate random filename to prevent path traversal."""
ext = os.path.splitext(filename)[1].lower()
return f"uploads/{uuid.uuid4().hex}{ext}"
class Document(models.Model):
file = models.FileField(
upload_to=upload_to,
validators=[validate_file_extension],
max_length=255,
)
Security Middleware Stack
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"csp.middleware.CSPMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
]
Dependency Auditing
pip-audit
safety check
pip-compile --generate-hashes requirements.in
pip install --require-hashes -r requirements.txt
Checklist