
Security Django
- 79 installs
- 125 repo stars
- Updated February 4, 2026
- igorwarzocha/opencode-workflows
Audit Django apps for SECRET_KEY/DEBUG/ALLOWED_HOSTS misconfig, missing auth decorators, CSRF issues, and raw SQL.
About
A security-audit skill for Django settings, middleware, and views. A developer uses it to review SECRET_KEY handling, DEBUG, CSRF config, auth decorators, and DRF default permissions.
- Flags hardcoded SECRET_KEY and unsafe DEBUG/ALLOWED_HOSTS
- Checks @login_required, @csrf_exempt, and raw SQL vs ORM
Security Django by the numbers
- 79 all-time installs (skills.sh)
- +5 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #1,099 of 2,203 Security skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/igorwarzocha/opencode-workflows --skill security-djangoAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 79 |
|---|---|
| repo stars | ★ 125 |
| Last updated | February 4, 2026 |
| Repository | igorwarzocha/opencode-workflows ↗ |
What it does
Audit Django apps for SECRET_KEY/DEBUG/ALLOWED_HOSTS misconfig, missing auth decorators, CSRF issues, and raw SQL.
Files
<overview>
Security audit patterns for Django applications covering critical settings, security middleware, CSRF protection, and common vulnerabilities.
</overview>
<rules>
Critical Settings (settings.py)
SECRET_KEY
# ❌ CRITICAL: Hardcoded or committed
SECRET_KEY = 'django-insecure-abc123...'
SECRET_KEY = 'my-super-secret-key'
# ✓ From environment
import os
SECRET_KEY = os.environ['DJANGO_SECRET_KEY']
# ✓ Or use django-environ
import environ
env = environ.Env()
SECRET_KEY = env('SECRET_KEY')Check: Is SECRET_KEY in .env and .env is in .gitignore?
DEBUG
# ❌ CRITICAL: Debug in production
DEBUG = True # Exposes full stack traces, settings, SQL queries
# ✓ Environment-controlled
DEBUG = os.environ.get('DEBUG', 'False').lower() == 'true'ALLOWED_HOSTS
# ❌ CRITICAL: Accept any host
ALLOWED_HOSTS = ['*']
# ❌ HIGH: Empty in production (500 errors, but still bad)
ALLOWED_HOSTS = []
# ✓ Explicit hosts
ALLOWED_HOSTS = ['example.com', 'www.example.com']Security Middleware
Required Middleware
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware', # MUST be first!
# ...
'django.middleware.csrf.CsrfViewMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]Check: Is SecurityMiddleware present and near the top?
Security Middleware Settings
# ✓ Enable these in production
SECURE_BROWSER_XSS_FILTER = True # Deprecated but harmless
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
SECURE_SSL_REDIRECT = True # Force HTTPS
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True</rules>
<vulnerabilities>
CSRF Protection
Disabled CSRF (Critical)
# ❌ CRITICAL: Globally disabled
MIDDLEWARE = [
# 'django.middleware.csrf.CsrfViewMiddleware', # Commented out!
]
# ❌ HIGH: Decorator abuse
@csrf_exempt
def payment_webhook(request): # MAY be OK for webhooks with other auth
...
@csrf_exempt
def update_profile(request): # MUST NOT do this!
...Audit: Search for @csrf_exempt - each needs justification.
CSRF Trusted Origins (Django 4.0+)
# ❌ Too permissive
CSRF_TRUSTED_ORIGINS = ['https://*']
# ✓ Explicit
CSRF_TRUSTED_ORIGINS = ['https://example.com', 'https://admin.example.com']Common Vulnerabilities
SQL Injection
# ❌ Raw SQL with string formatting
User.objects.raw(f"SELECT * FROM users WHERE id = {user_id}")
cursor.execute(f"DELETE FROM logs WHERE date < '{date}'")
# ✓ Parameterized
User.objects.raw("SELECT * FROM users WHERE id = %s", [user_id])
cursor.execute("DELETE FROM logs WHERE date < %s", [date])
# ✓ ORM (safe by default)
User.objects.filter(id=user_id)Command Injection
# ❌ User input in subprocess
import subprocess
subprocess.run(f"convert {user_filename} output.png", shell=True)
os.system(f"process {user_input}")
# ✓ Use arrays, avoid shell=True
subprocess.run(["convert", user_filename, "output.png"])Path Traversal
# ❌ User-controlled path
def download(request, filename):
return FileResponse(open(f'uploads/{filename}', 'rb'))
# ✓ Validate path
import os
def download(request, filename):
safe_name = os.path.basename(filename)
filepath = os.path.join(settings.UPLOAD_DIR, safe_name)
if not filepath.startswith(settings.UPLOAD_DIR):
raise Http404()
return FileResponse(open(filepath, 'rb'))IDOR (Insecure Direct Object Reference)
# ❌ No ownership check
class DocumentView(View):
def get(self, request, doc_id):
doc = Document.objects.get(id=doc_id)
return JsonResponse(doc.to_dict())
# ✓ Check ownership
class DocumentView(LoginRequiredMixin, View):
def get(self, request, doc_id):
doc = Document.objects.get(id=doc_id, owner=request.user)
return JsonResponse(doc.to_dict())Auth Decorators Missing
# ❌ No auth required
def admin_dashboard(request):
return render(request, 'admin/dashboard.html', {'users': User.objects.all()})
# ✓ Auth required
@login_required
@user_passes_test(lambda u: u.is_staff)
def admin_dashboard(request):
...Django REST Framework
# Check DRF settings
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
# ❌ SessionAuth without CSRF = vulnerable
'rest_framework.authentication.SessionAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': [
# ❌ Allow any by default
'rest_framework.permissions.AllowAny',
# ✓ Require auth by default
'rest_framework.permissions.IsAuthenticated',
],
}</vulnerabilities>
<commands>
Quick Audit Commands
# Check critical settings
rg "(SECRET_KEY|DEBUG|ALLOWED_HOSTS)" settings*.py
# Find csrf_exempt usage
rg "@csrf_exempt" . -g "*.py"
# Django deployment checklist (high signal)
python manage.py check --deploy
# Find raw SQL
rg "\.raw\(|cursor\.execute\(" . -g "*.py" -A 1
# Find subprocess/os.system
rg "(subprocess\.|os\.system|os\.popen)" . -g "*.py"
# Check for missing login_required
rg "^def " views.py | head -20 # Then check which have decorators
# Find shell=True
rg "shell\s*=\s*True" . -g "*.py"</commands>
<checklist>
Hardening Checklist
- [ ] SECRET_KEY from environment, not hardcoded
- [ ] DEBUG = False in production
- [ ] ALLOWED_HOSTS explicitly set (no wildcards)
- [ ] SecurityMiddleware enabled and configured
- [ ] CSRF middleware enabled
- [ ] SECURE_SSL_REDIRECT = True
- [ ] SESSION_COOKIE_SECURE = True
- [ ] CSRF_COOKIE_SECURE = True
- [ ] No @csrf_exempt without justification
- [ ] All views have appropriate auth decorators
- [ ] No raw SQL with string formatting
- [ ] DRF has IsAuthenticated as default permission
</checklist>
#!/usr/bin/env bash
# Django Security Scanner - First-pass automated detection
# Usage: ./scan.sh [directory]
set -euo pipefail
DIR="${1:-.}"
FOUND=0
echo "=== DJANGO SECURITY SCAN ==="
echo "Directory: $DIR"
echo "Timestamp: $(date -Iseconds)"
echo ""
if ! command -v rg &> /dev/null; then
echo "[ERROR] ripgrep (rg) required"
exit 1
fi
report() {
local severity="$1"
local title="$2"
local file="${3:-}"
local line="${4:-}"
echo "[$severity] $title"
if [[ -n "$file" ]]; then
if [[ -n "$line" ]]; then
echo " File: $file:$line"
else
echo " File: $file"
fi
fi
echo ""
FOUND=$((FOUND + 1))
}
# Find settings files
SETTINGS_FILES=$(find "$DIR" -name "settings*.py" -type f 2>/dev/null || true)
if [[ -z "$SETTINGS_FILES" ]]; then
echo "[INFO] No settings.py found. Is this a Django project?"
exit 0
fi
for SETTINGS in $SETTINGS_FILES; do
echo "=== Checking: $SETTINGS ==="
echo ""
# SECRET_KEY
echo "## SECRET_KEY"
if rg -q "SECRET_KEY.*=.*['\"][^'\"]+['\"]" "$SETTINGS" 2>/dev/null; then
if ! rg -q "SECRET_KEY.*os\.environ|SECRET_KEY.*env\(" "$SETTINGS" 2>/dev/null; then
report "CRITICAL" "SECRET_KEY appears hardcoded" "$SETTINGS"
fi
fi
# DEBUG
echo "## DEBUG"
if rg -q "^DEBUG\s*=\s*True" "$SETTINGS" 2>/dev/null; then
report "CRITICAL" "DEBUG = True (should be False in production)" "$SETTINGS"
fi
# ALLOWED_HOSTS
echo "## ALLOWED_HOSTS"
if rg -q "ALLOWED_HOSTS.*\*" "$SETTINGS" 2>/dev/null; then
report "CRITICAL" "ALLOWED_HOSTS contains wildcard" "$SETTINGS"
fi
if rg -q "ALLOWED_HOSTS\s*=\s*\[\s*\]" "$SETTINGS" 2>/dev/null; then
report "HIGH" "ALLOWED_HOSTS is empty" "$SETTINGS"
fi
# Security middleware
echo "## Security Middleware"
if ! rg -q "SecurityMiddleware" "$SETTINGS" 2>/dev/null; then
report "HIGH" "SecurityMiddleware not found in MIDDLEWARE" "$SETTINGS"
fi
# CSRF
echo "## CSRF"
if ! rg -q "CsrfViewMiddleware" "$SETTINGS" 2>/dev/null; then
report "CRITICAL" "CsrfViewMiddleware not found (CSRF disabled?)" "$SETTINGS"
fi
# Cookie security
echo "## Cookie Security"
if ! rg -q "SESSION_COOKIE_SECURE\s*=\s*True" "$SETTINGS" 2>/dev/null; then
report "MEDIUM" "SESSION_COOKIE_SECURE not set to True" "$SETTINGS"
fi
if ! rg -q "CSRF_COOKIE_SECURE\s*=\s*True" "$SETTINGS" 2>/dev/null; then
report "MEDIUM" "CSRF_COOKIE_SECURE not set to True" "$SETTINGS"
fi
if ! rg -q "SECURE_SSL_REDIRECT\s*=\s*True" "$SETTINGS" 2>/dev/null; then
report "MEDIUM" "SECURE_SSL_REDIRECT not set to True" "$SETTINGS"
fi
echo ""
done
echo "=== CRITICAL: csrf_exempt Usage ==="
echo ""
while IFS=: read -r file line match; do
[[ -z "$file" ]] && continue
report "HIGH" "@csrf_exempt decorator (needs justification)" "$file" "$line"
done < <(rg -n --no-heading '@csrf_exempt' "$DIR" -g "*.py" 2>/dev/null || true)
echo "=== HIGH: Raw SQL Queries ==="
echo ""
while IFS=: read -r file line match; do
[[ -z "$file" ]] && continue
report "HIGH" "Raw SQL query (check for string interpolation)" "$file" "$line"
done < <(rg -n --no-heading '\.raw\(|cursor\.execute\(' "$DIR" -g "*.py" 2>/dev/null || true)
echo "=== HIGH: Shell Commands ==="
echo ""
while IFS=: read -r file line match; do
[[ -z "$file" ]] && continue
if echo "$match" | grep -q "shell.*=.*True"; then
report "HIGH" "subprocess with shell=True (command injection risk)" "$file" "$line"
fi
done < <(rg -n --no-heading 'subprocess\.' "$DIR" -g "*.py" 2>/dev/null || true)
while IFS=: read -r file line match; do
[[ -z "$file" ]] && continue
report "HIGH" "os.system usage (command injection risk)" "$file" "$line"
done < <(rg -n --no-heading 'os\.system\(' "$DIR" -g "*.py" 2>/dev/null || true)
echo "=== MEDIUM: Missing Auth Decorators ==="
echo ""
# Find view functions without decorators (heuristic)
VIEW_FILES=$(find "$DIR" -name "views.py" -type f 2>/dev/null || true)
for view_file in $VIEW_FILES; do
# Count functions without common auth decorators above them
UNDECORATED=$(rg -B2 '^def [a-z_]+\(request' "$view_file" 2>/dev/null | grep -c '^def' || echo 0)
DECORATED=$(rg -B2 '^def [a-z_]+\(request' "$view_file" 2>/dev/null | grep -cE '@(login_required|permission_required|user_passes_test)' || echo 0)
if [[ $UNDECORATED -gt 0 && $DECORATED -lt $UNDECORATED ]]; then
report "MEDIUM" "Some view functions may lack auth decorators" "$view_file"
fi
done
echo "=== INFO: Deployment Checklist ==="
echo ""
echo "Consider running: python manage.py check --deploy"
echo ""
echo "=== SUMMARY ==="
if [[ $FOUND -gt 0 ]]; then
echo "[!] Found $FOUND potential issues. Review above."
exit 1
else
echo "[✓] No obvious Django security issues detected"
exit 0
fi