Missing Security Headers in Django
Django includes SecurityMiddleware that can set several security headers, but most are disabled by default. Settings like SECURE_HSTS_SECONDS, SECURE_CONTENT_TYPE_NOSNIFF, and X_FRAME_OPTIONS exist but require explicit configuration. Content-Security-Policy is not built into Django at all and requires the django-csp package.
Scan Your Django AppHow Missing Security Headers Manifests in Django
Django's SecurityMiddleware is included in MIDDLEWARE by default in new projects, but its header-related settings are all disabled or set to permissive defaults: SECURE_HSTS_SECONDS defaults to 0, meaning no Strict-Transport-Security header is sent. SECURE_CONTENT_TYPE_NOSNIFF defaults to True in Django 4.0+ but was False in earlier versions. X_FRAME_OPTIONS defaults to 'DENY' since Django 3.0 but was 'SAMEORIGIN' previously. Content-Security-Policy has no built-in support. Developers must install django-csp and configure it manually, which many skip because it requires understanding CSP directives. The Referrer-Policy and Permissions-Policy headers are not managed by Django at all and must be added through custom middleware or the django-permissions-policy package. Django REST Framework responses bypass template middleware, so DRF API views may not have the same headers as template-rendered views unless middleware is ordered correctly.
Real-World Impact
A Django application deployed with SECURE_HSTS_SECONDS = 0 was vulnerable to SSL stripping. An attacker on the same network as a user intercepted the initial HTTP request before it redirected to HTTPS, injecting a script that captured the user's session cookie. A Django CMS allowed editors to upload files that were served directly by the web server. Without X-Content-Type-Options: nosniff (which was disabled in older Django versions), browsers rendered uploaded HTML files as active content, allowing stored XSS through file uploads.
Step-by-Step Fix
Configure SecurityMiddleware settings
Enable all security-related settings in settings.py.
# settings.py
# Ensure SecurityMiddleware is in MIDDLEWARE
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
# ... other middleware
]
# HSTS - set to 1 year (enable after confirming HTTPS works)
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Prevent MIME sniffing
SECURE_CONTENT_TYPE_NOSNIFF = True
# Redirect HTTP to HTTPS
SECURE_SSL_REDIRECT = True
# Clickjacking protection
X_FRAME_OPTIONS = 'DENY'
# Secure cookies
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')Add Content-Security-Policy with django-csp
Install and configure django-csp to add CSP headers to all responses.
# pip install django-csp
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'csp.middleware.CSPMiddleware',
# ... other middleware
]
# CSP configuration
CSP_DEFAULT_SRC = ("'self'",)
CSP_SCRIPT_SRC = ("'self'",)
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'") # Needed for Django admin
CSP_IMG_SRC = ("'self'", 'data:', 'https:')
CSP_FONT_SRC = ("'self'",)
CSP_CONNECT_SRC = ("'self'",)
CSP_FRAME_ANCESTORS = ("'none'",)
# Start with report-only to avoid breaking your app
# CSP_REPORT_ONLY = True
# CSP_REPORT_URI = '/csp-report/'Add Referrer-Policy and Permissions-Policy
These headers are not built into Django. Add them via custom middleware or the django-permissions-policy package.
# Option 1: Custom middleware
# myapp/middleware.py
class ExtraSecurityHeadersMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
response['Permissions-Policy'] = 'camera=(), microphone=(), geolocation=()'
return response
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'myapp.middleware.ExtraSecurityHeadersMiddleware',
# ... other middleware
]
# Option 2: pip install django-permissions-policy
# PERMISSIONS_POLICY = {
# "camera": [],
# "microphone": [],
# "geolocation": [],
# }Prevention Best Practices
1. Enable all SecurityMiddleware settings in settings.py: SECURE_HSTS_SECONDS, SECURE_CONTENT_TYPE_NOSNIFF, SECURE_SSL_REDIRECT. 2. Install django-csp and configure Content-Security-Policy. 3. Add Referrer-Policy and Permissions-Policy through django-permissions-policy or custom middleware. 4. Verify headers apply to both Django views and DRF API responses. 5. Set SECURE_HSTS_SECONDS to a high value (31536000) once HTTPS is confirmed working.
How to Test
1. Run python manage.py check --deploy to see Django's built-in security warnings. 2. Use curl -I https://your-app.com/ to inspect response headers. 3. Verify that both HTML pages and DRF API endpoints include security headers. 4. Check that SECURE_HSTS_SECONDS is non-zero in production settings. 5. Use Vibe App Scanner to automatically detect missing security headers in your Django application.
Frequently Asked Questions
Does Django set security headers by default?
Django's SecurityMiddleware is included in new projects but most settings are disabled. X_FRAME_OPTIONS defaults to DENY since Django 3.0, and SECURE_CONTENT_TYPE_NOSNIFF defaults to True since Django 4.0. But HSTS (SECURE_HSTS_SECONDS), SSL redirect, and CSP are all disabled by default and require manual configuration.
Does the manage.py check --deploy command catch missing headers?
Yes, python manage.py check --deploy flags many common security misconfigurations including missing HSTS, insecure cookies, and disabled SSL redirect. However, it does not check for CSP, Referrer-Policy, or Permissions-Policy since those require third-party packages.
Will strict CSP break Django Admin?
Django Admin uses inline styles and inline scripts in some versions. You may need to add unsafe-inline to style-src or use nonce-based CSP. Test the admin panel thoroughly after enabling CSP. Consider using a less strict CSP for admin URLs via the @csp_update decorator from django-csp.
Related Security Resources
Is Your Django App Vulnerable to Missing Security Headers?
VAS automatically scans for missing security headers vulnerabilities in Django applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Django stack.