Communitygithub.com

django-security

Bonnes pratiques de sécurité Django : authentification, autorisation, protection CSRF, prévention des injections SQL et XSS, configurations de déploiement sécurisées. À utiliser lors de la révision de l'authentification, de l'autorisation, de la gestion des entrées ou des paramètres de déploiement d'une application Django.

Qu'est-ce que django-security ?

Ce skill fournit un guide complet de sécurité pour les applications Django, couvrant la configuration de production, l'authentification, l'autorisation, la prévention des injections SQL, du XSS, du CSRF, la sécurité des téléchargements de fichiers et la sécurité des API. Il est conçu pour les développeurs qui créent ou révisent des applications Django, en s'assurant qu'elles respectent les meilleures pratiques de sécurité. Il comprend des extraits de code prêts à l'emploi pour la configuration des paramètres de production, la mise en place de modèles utilisateur personnalisés, la gestion des permissions, la sécurisation des vues d'API avec des limites de débit et l'utilisation de l'ORM Django pour prévenir les injections SQL. Ce skill met l'accent sur l'importance de ne jamais faire confiance aux entrées utilisateur et de toujours utiliser les mécanismes de sécurité intégrés de Django. Il prend en charge les environnements utilisant Django, Django REST Framework, ainsi que les bibliothèques de hachage de mots de passe et de validation de fichiers.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/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

Guide complet de sécurité 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
  • Implémentation des permissions et des rôles utilisateur
  • Configuration des paramètres de sécurité pour la production
  • Révision 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 des paramètres de 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 l'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 une erreur 403 au lieu de rediriger

    def get_queryset(self):
        """Permet uniquement aux utilisateurs de 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 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 via l'ORM Django

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

# 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ûr

# 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ûr

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ÛR #}
{{ user_input }}  {# HTML échappé #}

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

{# Utiliser des 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 une entrée utilisateur comme sûre sans l'échapper
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 du HTML avec des 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êche le reniflage MIME
SECURE_BROWSER_XSS_FILTER = True  # Active le filtre XSS
X_FRAME_OPTIONS = 'DENY'  # Empêche 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êche l'accès JavaScript
CSRF_COOKIE_SAMESITE = 'Lax'  # Empêche 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">Envoyer</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)
});

Exempter des vues (À utiliser avec précaution)

from django.views.decorators.csrf import csrf_exempt

@csrf_exempt  # Utiliser uniquement en cas de nécessité absolue !
def webhook_view(request):
    # Webhook provenant d'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 au contenu du fichier.')

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 paquet 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 au contenu du fichier.')

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',
    }
}

# Limiteur personnalisé
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 secure_api(request):
    return Response({'message': 'Sécurisé'})

Déploiement sécurisé

Configuration du serveur web

# nginx.conf - Exemple pour Nginx
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-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header Content-Security-Policy "default-src 'self'";
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;

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

    location /media/ {
        alias /var/www/media/;
        expires 7d;
    }

    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;
    }
}

Conteneurisation Docker

# Dockerfile
FROM python:3.11-slim

RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    && rm -rf /var/lib/apt/lists/*

WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Exécuter en tant qu'utilisateur non root
RUN groupadd -r django && useradd -r -g django django
USER django

COPY . .

CMD ["gunicorn", "config.wsgi:application", "--bind", "0.0.0.0:8000", "--workers", "4"]
# docker-compose.yml
version: '3.8'

services:
  web:
    build: .
    ports:
      - "8000:8000"
    environment:
      - DJANGO_SECRET_KEY=${DJANGO_SECRET_KEY}
      - DATABASE_URL=postgres://user:password@db:5432/dbname
      - ALLOWED_HOSTS=example.com,www.example.com
    depends_on:
      - db

  db:
    image: postgres:15
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=dbname
    volumes:
      - postgres_data:/var/lib/postgresql/data
    ports:
      - "5432:5432"

  nginx:
    image: nginx:alpine
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf
      - static_volume:/var/www/static
      - media_volume:/var/www/media
    ports:
      - "80:80"
      - "443:443"
    depends_on:
      - web

volumes:
  postgres_data:
  static_volume:
  media_volume:

Configuration du logging

Sécurité : ne jamais enregistrer les mots de passe, tokens, clés API ou informations personnelles identifiables (PII) dans les logs.

# settings.py - Configuration sécurisée du logging
import os
from logging.handlers import RotatingFileHandler

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'verbose': {
            'format': '{levelname} {asctime} {module} {process:d} {thread:d} {message}',
            'style': '{',
        },
        'simple': {
            'format': '{levelname} {message}',
            'style': '{',
        },
    },
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse',
        },
        'no_sensitive_data': {
            '()': 'utils.logging.SensitiveDataFilter',
        },
    },
    'handlers': {
        'console': {
            'level': 'INFO',
            'class': 'logging.StreamHandler',
            'formatter': 'simple',
            'filters': ['no_sensitive_data'],
        },
        'file': {
            'level': 'WARNING',
            'class': 'logging.handlers.RotatingFileHandler',
            'filename': '/var/log/django/app.log',
            'maxBytes': 1024 * 1024 * 10,  # 10 Mo
            'backupCount': 5,
            'formatter': 'verbose',
            'filters': ['require_debug_false', 'no_sensitive_data'],
        },
    },
    'loggers': {
        'django': {
            'handlers': ['console', 'file'],
            'level': 'INFO',
            'propagate': True,
        },
        'django.security': {
            'handlers': ['file'],
            'level': 'WARNING',
            'propagate': False,
        },
        'django.request': {
            'handlers': ['file'],
            'level': 'ERROR',
            'propagate': False,
        },
    },
}

Filtre personnalisé pour les données sensibles

# utils/logging.py
import re
import logging

class SensitiveDataFilter(logging.Filter):
    """Filtre les données sensibles des logs."""

    SENSITIVE_PATTERNS = [
        (re.compile(r'password=[^&\s]+', re.IGNORECASE), 'password=***'),
        (re.compile(r'token=[^&\s]+', re.IGNORECASE), 'token=***'),
        (re.compile(r'api_key=[^&\s]+', re.IGNORECASE), 'api_key=***'),
        (re.compile(r'secret=[^&\s]+', re.IGNORECASE), 'secret=***'),
        (re.compile(r'\b\d{3}-\d{2}-\d{4}\b'), '***-**-****'),  # SSN
        (re.compile(r'\b\d{16}\b'), '****-****-****-****'),  # Numéro de carte
    ]

    def filter(self, record):
        if hasattr(record, 'msg') and isinstance(record.msg, str):
            for pattern, replacement in self.SENSITIVE_PATTERNS:
                record.msg = pattern.sub(replacement, record.msg)
        return True

Sécurité de la base de données

Connexions chiffrées

# settings.py - PostgreSQL
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql',
        'NAME': os.environ.get('DB_NAME'),
        'USER': os.environ.get('DB_USER'),
        'PASSWORD': os.environ.get('DB_PASSWORD'),
        'HOST': os.environ.get('DB_HOST'),
        'PORT': os.environ.get('DB_PORT', '5432'),
        'OPTIONS': {
            'sslmode': 'require',  # Exiger SSL
        },
    }
}

# MySQL équivalent
import pymysql
pymysql.install_as_MySQLdb()

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'ssl': {
                'ca': '/path/to/ca.pem',
                'cert': '/path/to/client-cert.pem',
                'key': '/path/to/client-key.pem',
            },
        },
    }
}

Chiffrement des données sensibles

# utils/encryption.py
from cryptography.fernet import Fernet
from django.conf import settings

def encrypt_value(value: str) -> str:
    """Chiffre une valeur sensible."""
    cipher = Fernet(settings.ENCRYPTION_KEY.encode())
    return cipher.encrypt(value.encode()).decode()

def decrypt_value(encrypted_value: str) -> str:
    """Déchiffre une valeur sensible."""
    cipher = Fernet(settings.ENCRYPTION_KEY.encode())
    return cipher.decrypt(encrypted_value.encode()).decode()

# models.py
from django.db import models
from utils.encryption import encrypt_value, decrypt_value

class SensitiveData(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    encrypted_ssn = models.TextField()  # Champ chiffré

    def set_ssn(self, ssn):
        self.encrypted_ssn = encrypt_value(ssn)

    def get_ssn(self):
        return decrypt_value(self.encrypted_ssn)

Audit et conformité

Suivi des actions utilisateur

# utils/audit.py
from django.db import models
from django.contrib.auth import get_user_model
from django.utils import timezone

class AuditLog(models.Model):
    user = models.ForeignKey(
        get_user_model(),
        on_delete=models.SET_NULL,
        null=True
    )
    action = models.CharField(max_length=50)
    model_name = models.CharField(max_length=100)
    object_id = models.PositiveIntegerField(null=True)
    changes = models.JSONField(null=True)
    ip_address = models.GenericIPAddressField(null=True)
    timestamp = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table = 'audit_logs'
        ordering = ['-timestamp']
        indexes = [
            models.Index(fields=['user', 'timestamp']),
            models.Index(fields=['action']),
        ]

# middleware de journalisation
from django.utils.deprecation import MiddlewareMixin

class AuditMiddleware(MiddlewareMixin):
    def process_request(self, request):
        request.audit = {
            'ip': request.META.get('REMOTE_ADDR'),
            'user_agent': request.META.get('HTTP_USER_AGENT'),
        }

Variables d'environnement

# .env.example - Variables d'environnement pour la production
DJANGO_SECRET_KEY=votre-clé-secrète-ici
DJANGO_DEBUG=False
DJANGO_ALLOWED_HOSTS=example.com,www.example.com
DJANGO_DATABASE_URL=postgres://user:password@localhost:5432/dbname
DJANGO_REDIS_URL=redis://localhost:6379/1
DJANGO_EMAIL_HOST=smtp.gmail.com
DJANGO_EMAIL_PORT=587
[email protected]
DJANGO_EMAIL_PASSWORD=your-app-password
DJANGO_ENCRYPTION_KEY=votre-clé-de-chiffrement

Dépendances essentielles

# requirements.txt
Django>=4.2,<5.0
djangorestframework>=3.14,<4.0
django-cors-headers>=4.0,<5.0
django-filter>=23.0,<24.0
django-axes>=6.0,<7.0  # Protection contre la force brute
django-ratelimit>=4.0,<5.0  # Limitation du débit des vues
bcrypt>=4.0,<5.0
argon2-cffi>=23.0,<24.0
python-magic>=0.4,<0.5  # Validation des types de fichiers (optionnel)
filetype>=1.2,<2.0  # Validation des fichiers en Python pur (optionnel)
cryptography>=41.0,<42.0  # Chiffrement
psycopg2-binary>=2.9,<3.0  # PostgreSQL
gunicorn>=21.0,<22.0  # Serveur WSGI pour la production
sentry-sdk>=1.30,<2.0  # Surveillance des erreurs

Liste de vérification de la sécurité

CatégorieÉlémentPriorité
ConfigurationDEBUG = False en productionCritique
ConfigurationSECRET_KEY définie via une variable d'environnementCritique
ConfigurationALLOWED_HOSTS correctement configuréeHaute
HTTPSSECURE_SSL_REDIRECT activéCritique
HTTPSHSTS configuréHaute
CookiesSESSION_COOKIE_SECURE et CSRF_COOKIE_SECURECritique
CookiesSESSION_COOKIE_HTTPONLY et CSRF_COOKIE_HTTPONLYHaute
CookiesSESSION_COOKIE_SAMESITE et CSRF_COOKIE_SAMESITEHaute
En-têtesX-Content-Type-Options, X-Frame-Options, CSPHaute
AuthentificationMot de passe minimum de 12 caractèresHaute
AuthentificationArgon2 comme hasher de mot de passeMoyenne
AuthentificationLimitation des tentatives de connexionHaute
Base de donnéesConnexions SSL/TLS en productionCritique
Base de donnéesUtiliser l'ORM/paramètres pour toutes les requêtesCritique
FichiersValider les types de fichiers par leur contenuHaute
FichiersLimiter la taille des fichiers téléchargésHaute
APIExiger une authentification pour les endpointsCritique
APIMettre en place une limitation du débitHaute
LogsNe jamais enregistrer les données sensiblesCritique
LogsConfigurer la rotation des logsMoyenne
DéploiementExécuter en tant qu'utilisateur non rootHaute
DéploiementUtiliser HTTPS avec des certificats validesCritique

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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Skills associés