Communitygithub.com

django-security

Bonnes pratiques de sécurité Django, authentification, autorisation, protection CSRF, prévention des injections SQL, prévention XSS et configurations de déploiement sécurisées. À utiliser lors de l'examen de l'authentification, de l'autorisation, du traitement des entrées ou des paramètres de déploiement Django.

Qu'est-ce que django-security ?

Ce skill fournit un guide complet et structuré pour sécuriser les applications Django, couvrant les vulnérabilités les plus courantes. Il détaille les configurations de production essentielles (DEBUG=False, ALLOWED_HOSTS, en-têtes de sécurité) et propose des implémentations prêtes à l'emploi pour l'authentification, l'autorisation, la prévention des injections SQL via l'ORM Django, la protection contre les XSS avec l'échappement automatique des templates, et la sécurisation CSRF. Le skill inclut également des bonnes pratiques pour la validation des fichiers uploadés, la limitation de débit des API et le hachage de mots de passe avec Argon2. Il est conçu pour les développeurs utilisant Django (Python) et peut être activé lors de l'audit de sécurité d'une application, de la configuration de la production ou de la mise en place de mécanismes d'authentification et d'autorisation. Il contient des exemples de code concrets (models, views, settings, templates) et des alternatives comme le package `filetype` pour les environnements contraints.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/django-security

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Bonnes pratiques de sécurité Django

Directives de sécurité complètes pour les applications Django afin de se protéger contre les vulnérabilités courantes.

Quand activer

  • Configuration de l'authentification et de l'autorisation Django
  • Mise en place des permissions et des rôles utilisateur
  • Configuration des paramètres de sécurité pour la production
  • Examen d'une application Django pour détecter des problèmes de sécurité
  • Déploiement d'applications Django en production

Paramètres de sécurité principaux

Configuration pour la production

# settings/production.py
import os

DEBUG = False  # CRITIQUE : Ne jamais utiliser True en production

ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')

# En-têtes de sécurité
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000  # 1 an
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'

# HTTPS et cookies
SESSION_COOKIE_HTTPONLY = True
CSRF_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SAMESITE = 'Lax'

# Clé secrète (doit être définie via une variable d'environnement)
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
    raise ImproperlyConfigured("La variable d'environnement DJANGO_SECRET_KEY est requise")

# Validation des mots de passe
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',
    },
]

Authentification

Modèle utilisateur personnalisé

# apps/users/models.py
from django.contrib.auth.models import AbstractUser
from django.db import models

class User(AbstractUser):
    """Modèle utilisateur personnalisé pour une meilleure sécurité."""

    email = models.EmailField(unique=True)
    phone = models.CharField(max_length=20, blank=True)

    USERNAME_FIELD = 'email'  # Utiliser l'email comme nom d'utilisateur
    REQUIRED_FIELDS = ['username']

    class Meta:
        db_table = 'users'
        verbose_name = 'Utilisateur'
        verbose_name_plural = 'Utilisateurs'

    def __str__(self):
        return self.email

# settings/base.py
AUTH_USER_MODEL = 'users.User'

Hachage des mots de passe

# Django utilise PBKDF2 par défaut. Pour une sécurité renforcée :
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]

Gestion des sessions

# Configuration des sessions
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # Ou 'db'
SESSION_CACHE_ALIAS = 'default'
SESSION_COOKIE_AGE = 3600 * 24 * 7  # 1 semaine
SESSION_SAVE_EVERY_REQUEST = False
SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # Meilleure UX, mais moins sécurisé

Autorisation

Permissions

# models.py
from django.db import models
from django.contrib.auth.models import Permission

class Post(models.Model):
    title = models.CharField(max_length=200)
    content = models.TextField()
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    class Meta:
        permissions = [
            ('can_publish', 'Peut publier des articles'),
            ('can_edit_others', 'Peut modifier les articles des autres'),
        ]

    def user_can_edit(self, user):
        """Vérifie si un utilisateur peut modifier cet article."""
        return self.author == user or user.has_perm('app.can_edit_others')

# views.py
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import UpdateView

class PostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
    model = Post
    permission_required = 'app.can_edit_others'
    raise_exception = True  # Renvoie 403 au lieu de rediriger

    def get_queryset(self):
        """Autorise uniquement les utilisateurs à modifier leurs propres articles."""
        return Post.objects.filter(author=self.request.user)

Permissions personnalisées

# permissions.py
from rest_framework import permissions

class IsOwnerOrReadOnly(permissions.BasePermission):
    """Autorise uniquement les propriétaires à modifier les objets."""

    def has_object_permission(self, request, view, obj):
        # Les permissions en lecture sont accordées à toute requête
        if request.method in permissions.SAFE_METHODS:
            return True

        # Les permissions en écriture sont réservées au propriétaire
        return obj.author == request.user

class IsAdminOrReadOnly(permissions.BasePermission):
    """Autorise les administrateurs à tout faire, les autres en lecture seule."""

    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        return request.user and request.user.is_staff

class IsVerifiedUser(permissions.BasePermission):
    """Autorise uniquement les utilisateurs vérifiés."""

    def has_permission(self, request, view):
        return request.user and request.user.is_authenticated and request.user.is_verified

Contrôle d'accès basé sur les rôles (RBAC)

# models.py
from django.contrib.auth.models import AbstractUser, Group

class User(AbstractUser):
    ROLE_CHOICES = [
        ('admin', 'Administrateur'),
        ('moderator', 'Modérateur'),
        ('user', 'Utilisateur standard'),
    ]
    role = models.CharField(max_length=20, choices=ROLE_CHOICES, default='user')

    def is_admin(self):
        return self.role == 'admin' or self.is_superuser

    def is_moderator(self):
        return self.role in ['admin', 'moderator']

# Mixins
class AdminRequiredMixin:
    """Mixin pour exiger le rôle d'administrateur."""

    def dispatch(self, request, *args, **kwargs):
        if not request.user.is_authenticated or not request.user.is_admin():
            from django.core.exceptions import PermissionDenied
            raise PermissionDenied
        return super().dispatch(request, *args, **kwargs)

Prévention des injections SQL

Protection par l'ORM Django

# BON : L'ORM Django échappe automatiquement les paramètres
def get_user(username):
    return User.objects.get(username=username)  # Sécurisé

# BON : Utilisation de paramètres avec raw()
def search_users(query):
    return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])

# MAUVAIS : Ne jamais interpoler directement les entrées utilisateur
def get_user_bad(username):
    return User.objects.raw(f'SELECT * FROM users WHERE username = {username}')  # VULNÉRABLE !

# BON : Utilisation de filter avec un échappement approprié
def get_users_by_email(email):
    return User.objects.filter(email__iexact=email)  # Sécurisé

# BON : Utilisation d'objets Q pour les requêtes complexes
from django.db.models import Q
def search_users_complex(query):
    return User.objects.filter(
        Q(username__icontains=query) |
        Q(email__icontains=query)
    )  # Sécurisé

Sécurité supplémentaire avec raw()

# Si vous devez utiliser du SQL brut, utilisez toujours des paramètres
User.objects.raw(
    'SELECT * FROM users WHERE email = %s AND status = %s',
    [user_input_email, status]
)

Prévention des XSS

Échappement dans les templates

{# Django échappe automatiquement les variables par défaut - SÉCURISÉ #}
{{ user_input }}  {# HTML échappé #}

{# Marquer explicitement comme sûr uniquement pour le contenu fiable #}
{{ trusted_html|safe }}  {# Non échappé #}

{# Utiliser les filtres de template pour un HTML sûr #}
{{ user_input|escape }}  {# Identique au comportement par défaut #}
{{ user_input|striptags }}  {# Supprime toutes les balises HTML #}

{# Échappement JavaScript #}
<script>
    var username = {{ username|escapejs }};
</script>

Gestion sécurisée des chaînes

from django.utils.safestring import mark_safe
from django.utils.html import escape

# MAUVAIS : Ne jamais marquer les entrées utilisateur comme sûres sans échappement
def render_bad(user_input):
    return mark_safe(user_input)  # VULNÉRABLE !

# BON : Échapper d'abord, puis marquer comme sûr
def render_good(user_input):
    return mark_safe(escape(user_input))

# BON : Utiliser format_html pour le HTML avec variables
from django.utils.html import format_html

def greet_user(username):
    return format_html('<span class="user">{}</span>', escape(username))

En-têtes HTTP

# settings.py
SECURE_CONTENT_TYPE_NOSNIFF = True  # Empêcher le reniflage MIME
SECURE_BROWSER_XSS_FILTER = True  # Activer le filtre XSS
X_FRAME_OPTIONS = 'DENY'  # Empêcher le clickjacking

# Middleware personnalisé
from django.conf import settings

class SecurityHeaderMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        response = self.get_response(request)
        response['X-Content-Type-Options'] = 'nosniff'
        response['X-Frame-Options'] = 'DENY'
        response['X-XSS-Protection'] = '1; mode=block'
        response['Content-Security-Policy'] = "default-src 'self'"
        return response

Protection CSRF

Protection CSRF par défaut

# settings.py - CSRF est activé par défaut
CSRF_COOKIE_SECURE = True  # Envoyer uniquement via HTTPS
CSRF_COOKIE_HTTPONLY = True  # Empêcher l'accès JavaScript
CSRF_COOKIE_SAMESITE = 'Lax'  # Empêcher CSRF dans certains cas
CSRF_TRUSTED_ORIGINS = ['https://example.com']  # Domaines de confiance

# Utilisation dans les templates
<form method="post">
    {% csrf_token %}
    {{ form.as_p }}
    <button type="submit">Soumettre</button>
</form>

# Requêtes AJAX
function getCookie(name) {
    let cookieValue = null;
    if (document.cookie && document.cookie !== '') {
        const cookies = document.cookie.split(';');
        for (let i = 0; i < cookies.length; i++) {
            const cookie = cookies[i].trim();
            if (cookie.substring(0, name.length + 1) === (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}

fetch('/api/endpoint/', {
    method: 'POST',
    headers: {
        'X-CSRFToken': getCookie('csrftoken'),
        'Content-Type': 'application/json',
    },
    body: JSON.stringify(data)
});

Exemption des vues (À utiliser avec précaution)

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt  # À utiliser uniquement en cas d'absolue nécessité !
def webhook_view(request):
    # Webhook depuis un service externe
    pass

Sécurité des téléchargements de fichiers

Validation des fichiers

import os
import magic  # pip install python-magic
from django.core.exceptions import ValidationError

ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf',
}

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """Valide le type de fichier à l'aide des octets magiques et vérifie l'extension."""
    mime = magic.from_buffer(value.read(2048), mime=True)
    value.seek(0)

    if mime not in ALLOWED_MIMES:
        raise ValidationError('Type de fichier non pris en charge.')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(mime, set()):
        raise ValidationError("L'extension du fichier ne correspond pas à son contenu.")

def validate_file_size(value):
    """Valide la taille du fichier (max 5 Mo)."""
    if value.size > 5 * 1024 * 1024:
        raise ValidationError('Fichier trop volumineux. La taille maximale est de 5 Mo.')

# models.py
class Document(models.Model):
    file = models.FileField(
        upload_to='documents/',
        validators=[validate_file_type, validate_file_size]
    )

Pour les environnements où l'installation de libmagic est difficile (par exemple, les conteneurs minimalistes), utilisez le package Python pur filetype comme alternative :

import os
from django.core.exceptions import ValidationError

import filetype  # pip install filetype

ALLOWED_MIMES = {
    'image/jpeg', 'image/png', 'image/gif', 'application/pdf',
}

MIME_TO_EXTENSIONS = {
    'image/jpeg': {'.jpg', '.jpeg'},
    'image/png': {'.png'},
    'image/gif': {'.gif'},
    'application/pdf': {'.pdf'},
}

def validate_file_type(value):
    """Valide le type de fichier à l'aide des octets magiques."""
    kind = filetype.guess(value.read(2048))
    value.seek(0)

    if kind is None or kind.mime not in ALLOWED_MIMES:
        raise ValidationError('Type de fichier non pris en charge.')

    ext = os.path.splitext(value.name)[1].lower()
    if ext not in MIME_TO_EXTENSIONS.get(kind.mime, set()):
        raise ValidationError("L'extension du fichier ne correspond pas à son contenu.")

Stockage sécurisé des fichiers

# settings.py
MEDIA_ROOT = '/var/www/media/'
MEDIA_URL = '/media/'

# Utiliser un domaine séparé pour les médias en production
MEDIA_DOMAIN = 'https://media.example.com'

# Ne pas servir les téléchargements utilisateur directement
# Utiliser whitenoise ou un CDN pour les fichiers statiques
# Utiliser un serveur séparé ou S3 pour les fichiers médias

Sécurité des API

Limitation du débit

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle'
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/jour',
        'user': '1000/jour',
        'upload': '10/heure',
    }
}

# Limitation personnalisée
from rest_framework.throttling import UserRateThrottle

class BurstRateThrottle(UserRateThrottle):
    scope = 'burst'
    rate = '60/min'

class SustainedRateThrottle(UserRateThrottle):
    scope = 'sustained'
    rate = '1000/jour'

Authentification pour les API

# settings.py
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
        'rest_framework.authentication.SessionAuthentication',
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# views.py
from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated

@api_view(['GET', 'POST'])
@permission_classes([IsAuthenticated])
def my_secure_view(request):
    return Response({"message": "Vous êtes authentifié"})

# Utilisation de JWT
from rest_framework_simplejwt.tokens import RefreshToken

def get_tokens_for_user(user):
    refresh = RefreshToken.for_user(user)
    return {
        'refresh': str(refresh),
        'access': str(refresh.access_token),
    }

Déploiement sécurisé

Configuration du serveur

# nginx.conf
server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/ssl/certs/example.com.crt;
    ssl_certificate_key /etc/ssl/private/example.com.key;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # En-têtes de sécurité
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline';" always;

    location /static/ {
        alias /var/www/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    location /media/ {
        alias /var/www/media/;
        # Ne pas permettre l'exécution de scripts dans les médias
        location ~ \.(php|py|pl)$ {
            deny all;
        }
    }

    location / {
        proxy_pass http://127.0.0.1:8000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Variables d'environnement

# .env.production
DEBUG=False
SECRET_KEY=votre-clé-secrète-très-longue-et-aléatoire
ALLOWED_HOSTS=example.com,www.example.com
DATABASE_URL=postgres://user:password@localhost:5432/dbname
REDIS_URL=redis://localhost:6379/0
[email protected]
EMAIL_HOST_PASSWORD=votre-mot-de-passe-d-application

# Configuration CDN
AWS_ACCESS_KEY_ID=votre-clef-aws
AWS_SECRET_ACCESS_KEY=votre-secret-aws
AWS_STORAGE_BUCKET_NAME=votre-bucket

Outils de sécurité

Vérification des dépendances

# Mettre à jour les dépendances régulièrement
pip install safety
safety check

# Vérifier les vulnérabilités connues
django-admin check --deploy

# Utiliser un outil SAST (Static Application Security Testing)
pip install bandit
bandit -r /path/to/your/project

Logging de sécurité

# settings.py
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
        },
        'file': {
            'class': 'logging.FileHandler',
            'filename': '/var/log/django/security.log',
            'level': 'WARNING',
        },
    },
    'loggers': {
        'django.security': {
            'handlers': ['file', 'console'],
            'level': 'WARNING',
            'propagate': True,
        },
        'django.request': {
            'handlers': ['file', 'console'],
            'level': 'ERROR',
            'propagate': False,
        },
    },
}

Références

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

Skills associés