Django security
Skill hlsitechio/claude-skills-security/appsec-stack-pack/django-security
Defensive security audit skills for Claude — tech-stack-keyed and audit-domain-keyed packs for SaaS apps.
npx -y skills add hlsitechio/claude-skills-security --skill django-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 1 stars1 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Security audit for Django applications including settings.py (SECRET_KEY, DEBUG, ALLOWED_HOSTS), middleware order, ORM raw queries, template autoescape bypass, CSRF protection, Django Admin exposure, authentication backends, file upload handling, and Django-specific patterns. Use this skill whenever the user mentions Django, settings.py, manage.py, Django ORM, Django REST Framework, DRF, makemigrations, urls.py, views.py, or asks "audit my Django app", "Django security review", "Django settings safe". Trigger when the codebase contains `django` in `requirements.txt` / `pyproject.toml`, or `manage.py`, `settings.py`, `urls.py` files.
SKILL.md
8.4 KB, as published. Nobody here has run it
Django Security Audit
Audit Django applications including DRF (Django REST Framework) APIs.
When this skill applies
- Reviewing Django
settings.pyfiles - Auditing views, models, and templates
- Reviewing DRF serializers and viewsets
- Checking authentication and permission classes
- Auditing Django Admin exposure
Workflow
Follow ../_shared/audit-workflow.md.
Phase 1: Stack detection
grep -E '^Django|django' requirements.txt pyproject.toml 2>/dev/null
find . -name 'manage.py' -not -path '*/.venv/*'
find . -name 'settings.py' -o -name 'settings/*.py' 2>/dev/null
python -c "import django; print(django.get_version())" 2>/dev/null
Phase 2: Inventory
# Settings files
find . -name 'settings*.py' -not -path '*/.venv/*' -not -path '*/node_modules/*'
# Views
find . -name 'views.py' -o -name 'views/' -type d 2>/dev/null
# URL configurations
find . -name 'urls.py' 2>/dev/null
# Raw SQL usage
grep -rn '\.raw(\|cursor()\|connection\.cursor' . --include='*.py' 2>/dev/null
# Template autoescape disabling
grep -rn '|safe\|autoescape off\|mark_safe\|format_html' . --include='*.py' --include='*.html' 2>/dev/null
Phase 3: Detection — the checks
settings.py audit
- DJG-SET-1
SECRET_KEYfrom environment variable, not committed:
Audit:SECRET_KEY = os.environ['DJANGO_SECRET_KEY']git log -p settings.py | grep -i secret_key— if any historical commit has a real secret, rotate. - DJG-SET-2
DEBUG = Falsein production. Confirmed via env-based settings split:DEBUG = os.environ.get('DJANGO_DEBUG', '0') == '1' - DJG-SET-3
ALLOWED_HOSTSnot['*']in production. Specific hostnames listed. - DJG-SET-4
SECURE_*settings configured:SECURE_SSL_REDIRECT = TrueSESSION_COOKIE_SECURE = TrueCSRF_COOKIE_SECURE = TrueSECURE_HSTS_SECONDS = 31536000(or more)SECURE_HSTS_INCLUDE_SUBDOMAINS = TrueSECURE_HSTS_PRELOAD = True(if you want preload list inclusion)SECURE_CONTENT_TYPE_NOSNIFF = TrueSECURE_BROWSER_XSS_FILTER— removed in Django 4 (XSS-Protection deprecated); use CSP insteadX_FRAME_OPTIONS = 'DENY'(or 'SAMEORIGIN')
- DJG-SET-5
CSRF_TRUSTED_ORIGINSlists specific origins (not*). - DJG-SET-6
DATABASESconfig uses env vars for credentials. - DJG-SET-7
EMAIL_*config doesn't have plaintext credentials. - DJG-SET-8
MIDDLEWAREorder:MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', # 1st 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', # before CommonMiddleware 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]
CSRF
- DJG-CSRF-1
CsrfViewMiddlewarein MIDDLEWARE. - DJG-CSRF-2 No
@csrf_exempton state-changing views unless explicitly needed and documented (webhooks with their own signature verification are OK). - DJG-CSRF-3 DRF views using
SessionAuthenticationautomatically apply CSRF. Token/JWT auth doesn't (and shouldn't, for non-cookie auth).
ORM and raw SQL
- DJG-ORM-1
.raw(sql, params)uses parameterized queries (theparamslist). String concatenation is SQL injection.# BAD User.objects.raw(f"SELECT * FROM auth_user WHERE id = {user_id}") # GOOD User.objects.raw("SELECT * FROM auth_user WHERE id = %s", [user_id]) - DJG-ORM-2
connection.cursor()queries use parameterized queries. - DJG-ORM-3
extra(where=[...])with user input is injection-prone; preferQ()filters. - DJG-ORM-4
objects.filter(...)with field lookups is safe; the ORM parameterizes.
Mass assignment
- DJG-MA-1
Model.objects.create(**request.POST)patterns flagged. Use forms / serializers with explicit field allowlists. - DJG-MA-2 DRF
ModelSerializerwithfields = '__all__'is mass-assignable. Use explicitfields = ['id', 'name', ...]. - DJG-MA-3 Form
Meta.fieldsandMeta.excludereviewed.
Templates
- DJG-TPL-1 Django templates auto-escape by default.
{{ var|safe }}filter and{% autoescape off %}disable it — review every occurrence. - DJG-TPL-2
mark_safe(...)andformat_html(...)in Python code mark strings as safe — confirm content is trusted. - DJG-TPL-3 SSTI: don't render user input as a template (
Template(user_input).render(...)).
Authentication
- DJG-AUTH-1
AUTH_PASSWORD_VALIDATORSconfigured with reasonable validators. - DJG-AUTH-2
PASSWORD_HASHERSfirst entry is Argon2 or BCrypt (Django default Argon2 is good). - DJG-AUTH-3 Custom backends (subclasses of
BaseBackend) reviewauthenticate()for timing attacks. - DJG-AUTH-4
django.contrib.auth.password_validation.validate_passwordcalled before set_password.
DRF (Django REST Framework)
- DJG-DRF-1 Global
DEFAULT_PERMISSION_CLASSESset toIsAuthenticated(or stricter); notAllowAny. - DJG-DRF-2 Per-view permission classes set; no view relying solely on URL routing for auth.
- DJG-DRF-3
ViewSet.querysetfiltered to tenant/user scope, not returning all objects. - DJG-DRF-4 Serializers define
fieldsexplicitly, exclude sensitive (password,is_superuser,user_permissionsif not needed). - DJG-DRF-5 Throttling configured (
DEFAULT_THROTTLE_RATES).
# REST_FRAMEWORK settings.py snippet
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework_simplejwt.authentication.JWTAuthentication',
],
'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
'DEFAULT_THROTTLE_CLASSES': [
'rest_framework.throttling.UserRateThrottle',
'rest_framework.throttling.AnonRateThrottle',
],
'DEFAULT_THROTTLE_RATES': {
'user': '1000/hour',
'anon': '100/hour',
},
}
File uploads
- DJG-UP-1
FILE_UPLOAD_MAX_MEMORY_SIZEandDATA_UPLOAD_MAX_MEMORY_SIZEset. - DJG-UP-2
FILE_UPLOAD_PERMISSIONSrestrictive (0o644). - DJG-UP-3
MEDIA_ROOTnot within web-served path that could expose uploaded files without auth. - DJG-UP-4 Uploaded file validated by content (magic bytes), not just
content_type.
Django Admin
- DJG-ADM-1 Admin reachable only by staff users (
is_staff=True). - DJG-ADM-2 Admin URL changed from
/admin/to a less-discoverable path (defense in depth). - DJG-ADM-3 Admin behind VPN / IP allowlist for production sites with regular user traffic.
- DJG-ADM-4 Custom admin actions reviewed for unintended privilege.
Static and media files
- DJG-STA-1
DEBUG = Falsemeans Django doesn't serve static files; deployed via nginx / S3 / CloudFront — confirm headers set there. - DJG-STA-2
collectstaticoutput doesn't include accidentally-committed sensitive files.
Logging
- DJG-LOG-1 Logging config doesn't include
'level': 'DEBUG'for production. Don't log request bodies containing PII. - DJG-LOG-2 Django's default email-error-to-admins (when DEBUG=False) sends tracebacks — verify recipients are correct, no broad mailing lists.
Dependencies
- DJG-DEP-1 Django version is on a current Long Term Support (LTS) line (4.2 LTS, 5.2 LTS) or current main. Old versions unpatched.
- DJG-DEP-2
pip list --outdatedreviewed.safety checkorpip-auditrun periodically.
Phase 4: Triage
Critical: DEBUG = True in production; ALLOWED_HOSTS = ['*']; raw query with string concatenation; admin publicly accessible with weak password policy.
Phase 5: Report
Use ../_shared/findings-schema.md. Prefix IDs with DJG-.