Django security scan
Skill Dolphinllc/claude-security-skills/skills/defensive/web/django-security-scan
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.From its SKILL.md
npx -y skills add Dolphinllc/claude-security-skills --skill django-security-scanAssembled 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.
SKILL.md
4.7 KB, ~1.2k tokens by cl100k_base, as published. Nobody here has run it
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/*.pyviews.py, class-based views, DRFViewSets andSerializersurls.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 |
| DJ-TPL-002 | medium | Template uses {% autoescape off %} block containing variables | Re-enable autoescape; use ` |
| DJ-DRF-001 | critical | DRF view permission_classes = [AllowAny] (or default) on mutating endpoint | Use IsAuthenticated (+ object-level perms) |
| DJ-DRF-002 | high | ModelSerializer with fields = "__all__" on User/auth/PII models | Enumerate fields; exclude password, is_staff, etc. |
| DJ-DRF-003 | medium | SearchFilter / OrderingFilter exposing fields not safe to enumerate (e.g., password_hash) | Define explicit search_fields / ordering_fields |
| DJ-AUTH-001 | high | LOGIN_URL view has no rate limiting / lockout (no django-axes / django-ratelimit) | Add django-axes |
| DJ-FILE-001 | high | FileField / ImageField without validators and used without MIME/size check on upload | Validate extension + content-type + size; store outside web root |
| DJ-CORS-001 | high | CORS_ALLOW_ALL_ORIGINS = True together with CORS_ALLOW_CREDENTIALS = True | Use CORS_ALLOWED_ORIGINS = [...] |
Wrong vs. right
DJ-DRF-002 (serializer leaks)
# ❌ Exposes password hash, is_staff, etc.
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "__all__"
# ✅ Explicit allowlist
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)
# ❌ Stored XSS pipeline
context["bio"] = mark_safe(user.bio)
# ✅ Let the template engine escape
context["bio"] = user.bio # rendered as {{ bio }} — auto-escaped
References
- Django security: https://docs.djangoproject.com/en/stable/topics/security/
- Deployment checklist: https://docs.djangoproject.com/en/stable/howto/deployment/checklist/
- DRF permissions: https://www.django-rest-framework.org/api-guide/permissions/
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.