| name | django-security-scan |
| description | Defensive security scan for Django and Django REST Framework projects. Detects DEBUG=True in production, wildcard ALLOWED_HOSTS, SECRET_KEY in source, missing CSRF, raw ORM queries with string formatting, mark_safe on user input, AllowAny on mutating DRF views, and ModelSerializer fields="__all__" leaking sensitive fields. Invoke when the user asks to "review", "audit", or "scan" a Django project. Use when this capability is needed. |
| metadata | {"author":"Dolphinllc"} |
Django Security Scan
Defensive scan for Django (4.2+/5.x) and Django REST Framework projects. Reports findings using the shared scoring schema.
Scope
settings.py / settings/*.py
views.py, class-based views, DRF ViewSets and Serializers
urls.py, middleware.py, custom managers and querysets
- Templates with
{% autoescape off %} / |safe
Procedure
- Read settings and assess configuration first.
- Walk views and serializers for permissions and validation.
- Grep for
raw(, extra(, mark_safe, format_html, |safe.
Rules
| ID | Severity | Detection | Fix |
|---|
| DJ-CFG-001 | critical | DEBUG = True not gated by env in any settings module loaded in prod | DEBUG = os.environ.get("DJANGO_DEBUG") == "1" |
| DJ-CFG-002 | critical | ALLOWED_HOSTS = ["*"] | Pin to canonical hostnames |
| DJ-CFG-003 | critical | SECRET_KEY = "..." literal in repo | Read from env / secret manager; rotate immediately |
| DJ-CFG-004 | high | SECURE_SSL_REDIRECT, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE not all True in prod | Set all three |
| DJ-CFG-005 | high | SECURE_HSTS_SECONDS = 0 (or unset) on a TLS site | Set ≥ 31536000 with subdomains/preload as appropriate |
| DJ-CFG-006 | medium | X_FRAME_OPTIONS removed or default SAMEORIGIN overridden to ALLOWALL | Keep DENY unless embedding is needed |
| DJ-MW-001 | high | MIDDLEWARE missing CsrfViewMiddleware or XFrameOptionsMiddleware | Restore default middleware order |
| DJ-CSRF-001 | high | @csrf_exempt on state-changing view that uses session auth | Remove decorator; if API, switch to token/header auth |
| DJ-ORM-001 | critical | Model.objects.raw(f"...{var}...") / cursor.execute(f"...") | Use parameterized: raw("SELECT ... WHERE id=%s", [var]) |
| DJ-ORM-002 | high | .extra(where=[f"col = '{var}'"]) | Use .filter() ORM constructs or parameterize |
| DJ-TPL-001 | high | mark_safe(user_input) / format_html("{}", user_input) where {} is unescaped intentionally | Render via template auto-escaping; never mark_safe user input |
|
Wrong vs. right
DJ-DRF-002 (serializer leaks)
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ["id", "username", "email", "date_joined"]
read_only_fields = ["id", "date_joined"]
DJ-ORM-001 (raw SQL)
User.objects.raw(f"SELECT * FROM auth_user WHERE email = '{email}'")
User.objects.raw("SELECT * FROM auth_user WHERE email = %s", [email])
DJ-TPL-001 (mark_safe)
context["bio"] = mark_safe(user.bio)
context["bio"] = user.bio
References
Source: Dolphinllc/claude-security-skills — distributed by TomeVault.