Application Security

Django Security and Supply Chain Guide

Securing Django applications with built-in security features, dependency management, and supply chain protections.

Nayan Dey
DevSecOps Lead
4 min read

Django markets itself as "the web framework for perfectionists with deadlines," and its security posture backs that up. Django has built-in CSRF protection, SQL injection prevention, XSS escaping, and clickjacking protection. But the framework's security only covers the code Django ships. Your dependencies, configuration, and deployment choices are on you.

Django's Built-In Security Features

CSRF Protection

Django includes CSRF middleware by default. Every POST request must include a CSRF token:

<form method="post">
  {% csrf_token %}
  <input type="text" name="username">
  <button type="submit">Submit</button>
</form>

For API views, if you are using Django REST Framework with session authentication, CSRF is enforced. With token authentication, it is not needed (the token itself is the proof).

Common mistake: disabling CSRF protection entirely because "we're an API." If any of your endpoints use cookie-based authentication, you need CSRF.

# DON'T do this
@csrf_exempt
def my_view(request):
    ...

SQL Injection Protection

Django's ORM parameterizes all queries:

# Safe: parameterized
User.objects.filter(username=user_input)

# Safe: parameterized even with complex lookups
User.objects.filter(username__icontains=search_term)

The danger comes from raw SQL:

# DANGEROUS: string formatting
User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{user_input}'")

# SAFE: parameterized raw SQL
User.objects.raw("SELECT * FROM auth_user WHERE username = %s", [user_input])

Also watch out for extra() and RawSQL(). Both accept raw SQL fragments and can be injection points.

Template Auto-Escaping

Django templates escape HTML by default:

{{ user_input }}  <!-- Escaped: safe -->
{{ user_input|safe }}  <!-- NOT escaped: dangerous -->
{% autoescape off %}{{ user_input }}{% endautoescape %}  <!-- NOT escaped -->

Audit your templates for |safe filters and {% autoescape off %} blocks. Each one is a potential XSS vector.

Security Settings Checklist

Your settings.py should include these security settings in production:

# Security settings for production
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']  # Never hardcode

# HTTPS settings
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

# Cookie security
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True

# Content security
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'

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

Run Django's security check:

python manage.py check --deploy

This flags common security misconfigurations.

Dependency Management

Django projects use pip and the Python ecosystem. Apply all standard Python dependency practices:

Pin everything

# requirements.txt (generated by pip-compile)
django==4.2.7
djangorestframework==3.14.0
celery==5.3.4
redis==5.0.1
psycopg2-binary==2.9.9

Use pip-compile with --generate-hashes for maximum security:

pip-compile requirements.in --generate-hashes --output-file requirements.txt

Audit regularly

pip-audit -r requirements.txt

Keep Django updated

Django has a clear security policy. Security releases come on a predictable schedule, and only supported versions receive patches. Check the Django version support matrix and ensure you are not running an unsupported release.

Third-Party App Security

Django's "batteries included" philosophy means many features are built in. But most projects still add third-party apps. Common ones like django-cors-headers, django-allauth, and django-storages are well-maintained, but each one is an additional dependency to manage.

Before adding a Django app:

  1. Check its PyPI page for maintenance status.
  2. Review its GitHub issues for security-related discussions.
  3. Check if it is compatible with your Django version.
  4. Assess its dependency tree.

Django REST Framework Security

If you use DRF, configure authentication and permissions explicitly:

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.TokenAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '100/hour',
        'user': '1000/hour',
    },
}

Never leave DEFAULT_PERMISSION_CLASSES empty or set to AllowAny in production.

SBOM Generation

Generate SBOMs for your Django project:

pip install cyclonedx-bom
cyclonedx-py environment --output sbom.json

How Safeguard.sh Helps

Safeguard.sh provides continuous monitoring for your Django application dependencies. It tracks Django framework versions across your services, alerts you when security releases are published, and flags applications running unsupported Django versions. Beyond the framework itself, Safeguard.sh monitors your entire Python dependency tree, from DRF to Celery to every transitive dependency, giving your team a single dashboard for Django supply chain risk across your organization.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.