Source profileQuality 80/100

affaan-m/ECC/skills/django-security/SKILL.md

django-security

Django security best practices, authentication, authorization, CSRF protection, SQL injection prevention, XSS prevention, and secure deployment configurations.

Source repository stars
234,327
Declared platforms
0
Static risk flags
1
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Comprehensive security guidelines for Django applications to protect against common vulnerabilities.

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorNot declaredNo explicit evidencePortability before use
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "skills/django-security"
    Safe inspection promptEditorial

    Inspect the Agent Skill "django-security" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/django-security/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      Template usage

      {% csrftoken %} {{ form.asp }} Submit

      {% csrftoken %} {{ form.asp }} Submit
    2. 02

      When to Activate

      Setting up Django authentication and authorization

      Setting up Django authentication and authorizationImplementing user permissions and rolesConfiguring production security settings
    3. 03

      Core Security Settings

      Review the “Core Security Settings” section in the pinned source before continuing.

      Review and apply the “Core Security Settings” source section.
    4. 04

      Production Settings Configuration

      Review the “Production Settings Configuration” section in the pinned source before continuing.

      Review and apply the “Production Settings Configuration” source section.
    5. 05

      settings/production.py

      DEBUG = False CRITICAL: Never use True in production

      DEBUG = False CRITICAL: Never use True in productionALLOWEDHOSTS = os.environ.get('ALLOWEDHOSTS', '').split(',')

    Permission review

    Static risk signals and limitations

    Network access

    medium · line 336

    The documentation includes network, browsing, or remote request actions.

    CSRF_TRUSTED_ORIGINS = ['https://example.com'] # Trusted domains

    Network access

    medium · line 361

    The documentation includes network, browsing, or remote request actions.

    fetch('/api/endpoint/', {

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score80/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility0 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    skills/django-security/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Django Security Best Practices

    Comprehensive security guidelines for Django applications to protect against common vulnerabilities.

    When to Activate

    • Setting up Django authentication and authorization
    • Implementing user permissions and roles
    • Configuring production security settings
    • Reviewing Django application for security issues
    • Deploying Django applications to production

    Core Security Settings

    Production Settings Configuration

    # settings/production.py
    import os
    
    DEBUG = False  # CRITICAL: Never use True in production
    
    ALLOWED_HOSTS = os.environ.get('ALLOWED_HOSTS', '').split(',')
    
    # Security headers
    SECURE_SSL_REDIRECT = True
    SESSION_COOKIE_SECURE = True
    CSRF_COOKIE_SECURE = True
    SECURE_HSTS_SECONDS = 31536000  # 1 year
    SECURE_HSTS_INCLUDE_SUBDOMAINS = True
    SECURE_HSTS_PRELOAD = True
    SECURE_CONTENT_TYPE_NOSNIFF = True
    SECURE_BROWSER_XSS_FILTER = True
    X_FRAME_OPTIONS = 'DENY'
    
    # HTTPS and Cookies
    SESSION_COOKIE_HTTPONLY = True
    CSRF_COOKIE_HTTPONLY = True
    SESSION_COOKIE_SAMESITE = 'Lax'
    CSRF_COOKIE_SAMESITE = 'Lax'
    
    # Secret key (must be set via environment variable)
    SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
    if not SECRET_KEY:
        raise ImproperlyConfigured('DJANGO_SECRET_KEY environment variable is required')
    
    # Password validation
    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',
        },
    ]
    

    Authentication

    Custom User Model

    # apps/users/models.py
    from django.contrib.auth.models import AbstractUser
    from django.db import models
    
    class User(AbstractUser):
        """Custom user model for better security."""
    
        email = models.EmailField(unique=True)
        phone = models.CharField(max_length=20, blank=True)
    
        USERNAME_FIELD = 'email'  # Use email as username
        REQUIRED_FIELDS = ['username']
    
        class Meta:
            db_table = 'users'
            verbose_name = 'User'
            verbose_name_plural = 'Users'
    
        def __str__(self):
            return self.email
    
    # settings/base.py
    AUTH_USER_MODEL = 'users.User'
    

    Password Hashing

    # Django uses PBKDF2 by default. For stronger security:
    PASSWORD_HASHERS = [
        'django.contrib.auth.hashers.Argon2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    ]
    

    Session Management

    # Session configuration
    SESSION_ENGINE = 'django.contrib.sessions.backends.cache'  # Or 'db'
    SESSION_CACHE_ALIAS = 'default'
    SESSION_COOKIE_AGE = 3600 * 24 * 7  # 1 week
    SESSION_SAVE_EVERY_REQUEST = False
    SESSION_EXPIRE_AT_BROWSER_CLOSE = False  # Better UX, but less secure
    

    Authorization

    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', 'Can publish posts'),
                ('can_edit_others', 'Can edit posts of others'),
            ]
    
        def user_can_edit(self, user):
            """Check if user can edit this post."""
            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  # Return 403 instead of redirect
    
        def get_queryset(self):
            """Only allow users to edit their own posts."""
            return Post.objects.filter(author=self.request.user)
    

    Custom Permissions

    # permissions.py
    from rest_framework import permissions
    
    class IsOwnerOrReadOnly(permissions.BasePermission):
        """Allow only owners to edit objects."""
    
        def has_object_permission(self, request, view, obj):
            # Read permissions allowed for any request
            if request.method in permissions.SAFE_METHODS:
                return True
    
            # Write permissions only for owner
            return obj.author == request.user
    
    class IsAdminOrReadOnly(permissions.BasePermission):
        """Allow admins to do anything, others read-only."""
    
        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):
        """Allow only verified users."""
    
        def has_permission(self, request, view):
            return request.user and request.user.is_authenticated and request.user.is_verified
    

    Role-Based Access Control (RBAC)

    # models.py
    from django.contrib.auth.models import AbstractUser, Group
    
    class User(AbstractUser):
        ROLE_CHOICES = [
            ('admin', 'Administrator'),
            ('moderator', 'Moderator'),
            ('user', 'Regular User'),
        ]
        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 to require admin role."""
    
        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)
    

    SQL Injection Prevention

    Django ORM Protection

    # GOOD: Django ORM automatically escapes parameters
    def get_user(username):
        return User.objects.get(username=username)  # Safe
    
    # GOOD: Using parameters with raw()
    def search_users(query):
        return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])
    
    # BAD: Never directly interpolate user input
    def get_user_bad(username):
        return User.objects.raw(f'SELECT * FROM users WHERE username = {username}')  # VULNERABLE!
    
    # GOOD: Using filter with proper escaping
    def get_users_by_email(email):
        return User.objects.filter(email__iexact=email)  # Safe
    
    # GOOD: Using Q objects for complex queries
    from django.db.models import Q
    def search_users_complex(query):
        return User.objects.filter(
            Q(username__icontains=query) |
            Q(email__icontains=query)
        )  # Safe
    

    Extra Security with raw()

    # If you must use raw SQL, always use parameters
    User.objects.raw(
        'SELECT * FROM users WHERE email = %s AND status = %s',
        [user_input_email, status]
    )
    

    XSS Prevention

    Template Escaping

    {# Django auto-escapes variables by default - SAFE #}
    {{ user_input }}  {# Escaped HTML #}
    
    {# Explicitly mark safe only for trusted content #}
    {{ trusted_html|safe }}  {# Not escaped #}
    
    {# Use template filters for safe HTML #}
    {{ user_input|escape }}  {# Same as default #}
    {{ user_input|striptags }}  {# Remove all HTML tags #}
    
    {# JavaScript escaping #}
    <script>
        var username = {{ username|escapejs }};
    </script>
    

    Safe String Handling

    from django.utils.safestring import mark_safe
    from django.utils.html import escape
    
    # BAD: Never mark user input as safe without escaping
    def render_bad(user_input):
        return mark_safe(user_input)  # VULNERABLE!
    
    # GOOD: Escape first, then mark safe
    def render_good(user_input):
        return mark_safe(escape(user_input))
    
    # GOOD: Use format_html for HTML with variables
    from django.utils.html import format_html
    
    def greet_user(username):
        return format_html('<span class="user">{}</span>', escape(username))
    

    HTTP Headers

    # settings.py
    SECURE_CONTENT_TYPE_NOSNIFF = True  # Prevent MIME sniffing
    SECURE_BROWSER_XSS_FILTER = True  # Enable XSS filter
    X_FRAME_OPTIONS = 'DENY'  # Prevent clickjacking
    
    # Custom middleware
    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
    

    CSRF Protection

    Default CSRF Protection

    # settings.py - CSRF is enabled by default
    CSRF_COOKIE_SECURE = True  # Only send over HTTPS
    CSRF_COOKIE_HTTPONLY = True  # Prevent JavaScript access
    CSRF_COOKIE_SAMESITE = 'Lax'  # Prevent CSRF in some cases
    CSRF_TRUSTED_ORIGINS = ['https://example.com']  # Trusted domains
    
    # Template usage
    <form method="post">
        {% csrf_token %}
        {{ form.as_p }}
        <button type="submit">Submit</button>
    </form>
    
    # AJAX requests
    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)
    });
    

    Exempting Views (Use Carefully)

    from django.views.decorators.csrf import csrf_exempt
    
    @csrf_exempt  # Only use when absolutely necessary!
    def webhook_view(request):
        # Webhook from external service
        pass
    

    File Upload Security

    File Validation

    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):
        """Validate file type using magic bytes and cross-check extension."""
        mime = magic.from_buffer(value.read(2048), mime=True)
        value.seek(0)
    
        if mime not in ALLOWED_MIMES:
            raise ValidationError('Unsupported file type.')
    
        ext = os.path.splitext(value.name)[1].lower()
        if ext not in MIME_TO_EXTENSIONS.get(mime, set()):
            raise ValidationError('File extension does not match file content.')
    
    def validate_file_size(value):
        """Validate file size (max 5MB)."""
        if value.size > 5 * 1024 * 1024:
            raise ValidationError('File too large. Max size is 5MB.')
    
    # models.py
    class Document(models.Model):
        file = models.FileField(
            upload_to='documents/',
            validators=[validate_file_type, validate_file_size]
        )
    
    

    For environments where installing libmagic is difficult (e.g., minimal containers), use the pure-Python filetype package as an 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):
        """Validate file type using magic bytes."""
        kind = filetype.guess(value.read(2048))
        value.seek(0)
    
        if kind is None or kind.mime not in ALLOWED_MIMES:
            raise ValidationError('Unsupported file type.')
    
        ext = os.path.splitext(value.name)[1].lower()
        if ext not in MIME_TO_EXTENSIONS.get(kind.mime, set()):
            raise ValidationError('File extension does not match file content.')
    

    Secure File Storage

    # settings.py
    MEDIA_ROOT = '/var/www/media/'
    MEDIA_URL = '/media/'
    
    # Use a separate domain for media in production
    MEDIA_DOMAIN = 'https://media.example.com'
    
    # Don't serve user uploads directly
    # Use whitenoise or a CDN for static files
    # Use a separate server or S3 for media files
    

    API Security

    Rate Limiting

    # settings.py
    REST_FRAMEWORK = {
        'DEFAULT_THROTTLE_CLASSES': [
            'rest_framework.throttling.AnonRateThrottle',
            'rest_framework.throttling.UserRateThrottle'
        ],
        'DEFAULT_THROTTLE_RATES': {
            'anon': '100/day',
            'user': '1000/day',
            'upload': '10/hour',
        }
    }
    
    # Custom throttle
    from rest_framework.throttling import UserRateThrottle
    
    class BurstRateThrottle(UserRateThrottle):
        scope = 'burst'
        rate = '60/min'
    
    class SustainedRateThrottle(UserRateThrottle):
        scope = 'sustained'
        rate = '1000/day'
    

    Authentication for APIs

    # 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 protected_view(request):
        return Response({'message': 'You are authenticated'})
    

    Security Headers

    Content Security Policy

    # settings.py
    CSP_DEFAULT_SRC = "'self'"
    CSP_SCRIPT_SRC = "'self' https://cdn.example.com"
    CSP_STYLE_SRC = "'self' 'unsafe-inline'"
    CSP_IMG_SRC = "'self' data: https:"
    CSP_CONNECT_SRC = "'self' https://api.example.com"
    
    # Middleware
    class CSPMiddleware:
        def __init__(self, get_response):
            self.get_response = get_response
    
        def __call__(self, request):
            response = self.get_response(request)
            response['Content-Security-Policy'] = (
                f"default-src {CSP_DEFAULT_SRC}; "
                f"script-src {CSP_SCRIPT_SRC}; "
                f"style-src {CSP_STYLE_SRC}; "
                f"img-src {CSP_IMG_SRC}; "
                f"connect-src {CSP_CONNECT_SRC}"
            )
            return response
    

    Environment Variables

    Managing Secrets

    # Use python-decouple or django-environ
    import environ
    
    env = environ.Env(
        # set casting, default value
        DEBUG=(bool, False)
    )
    
    # reading .env file
    environ.Env.read_env()
    
    SECRET_KEY = env('DJANGO_SECRET_KEY')
    DATABASE_URL = env('DATABASE_URL')
    ALLOWED_HOSTS = env.list('ALLOWED_HOSTS')
    
    # .env file (never commit this)
    DEBUG=False
    SECRET_KEY=your-secret-key-here
    DATABASE_URL=postgresql://user:password@localhost:5432/dbname
    ALLOWED_HOSTS=example.com,www.example.com
    

    Logging Security Events

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

    Quick Security Checklist

    CheckDescription
    DEBUG = FalseNever run with DEBUG in production
    HTTPS onlyForce SSL, secure cookies
    Strong secretsUse environment variables for SECRET_KEY
    Password validationEnable all password validators
    CSRF protectionEnabled by default, don't disable
    XSS preventionDjango auto-escapes, don't use &#124;safe with user input
    SQL injectionUse ORM, never concatenate strings in queries
    File uploadsValidate file type and size
    Rate limitingThrottle API endpoints
    Security headersCSP, X-Frame-Options, HSTS
    LoggingLog security events
    UpdatesKeep Django and dependencies updated

    Remember: Security is a process, not a product. Regularly review and update your security practices.

    Alternatives

    Compare before choosing