| name | security-first-validator |
| description | This skill should be used when the user asks to "create an API endpoint", "add a view", "write a viewset", "create a serializer", or when writing Django REST Framework code. Enforces security requirements (permissions, authentication, rate limiting). |
Security-First Validator
Enforces security requirements for all Django REST Framework API endpoints.
Activation Triggers
This skill activates when:
- Creating API views or viewsets
- Creating serializers
- Mentioning "endpoint", "API", "view", "route"
- Writing DRF classes (APIView, ViewSet, Serializer)
- Creating URL patterns for APIs
- Discussing authentication or permissions
Security Requirements (MANDATORY)
Every API endpoint MUST have:
1. Permission Classes (REQUIRED)
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
class UserViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]
2. Serializer Validation (REQUIRED)
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'email', 'first_name']
def validate_email(self, value):
if not value.endswith('@company.com'):
raise serializers.ValidationError("Must use company email")
return value
3. No Raw SQL (REQUIRED)
User.objects.raw(f"SELECT * FROM users WHERE id = {user_id}")
User.objects.filter(id=user_id)
4. Rate Limiting (REQUIRED for sensitive endpoints)
from rest_framework.throttling import UserRateThrottle
class LoginView(APIView):
throttle_classes = [UserRateThrottle]
Validation Process
Step 1: Detect New API Endpoint
When API code is being written:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
Step 2: Run Security Checklist
Verify:
- ✅ Permission classes defined
- ✅ Serializer has proper validation
- ✅ No raw SQL queries
- ✅ Rate limiting on sensitive endpoints
- ✅ No password/token fields in serializer
- ✅ Proper CORS configuration
Step 3: Auto-Add Security Requirements
Before (Insecure):
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
serializer_class = UserSerializer
After (Secure):
from rest_framework import viewsets
from rest_framework.permissions import IsAuthenticated
class UserViewSet(viewsets.ModelViewSet):
"""User management endpoints."""
queryset = User.objects.all()
serializer_class = UserSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Filter queryset to exclude soft-deleted records."""
return super().get_queryset().filter(is_deleted=False)
Step 4: Validate Serializer Security
Check for exposed sensitive fields:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
Auto-fix to:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'email', 'first_name', 'last_name']
read_only_fields = ['id', 'created_at', 'updated_at']
Step 5: Report Security Issues
Report to developer:
Security Issues Detected and Fixed
Fixed:
- ✅ Added
permission_classes = [IsAuthenticated] to UserViewSet
- ✅ Changed serializer from
fields = '__all__' to explicit field list
- ✅ Excluded sensitive fields (password, tokens)
- ✅ Added
read_only_fields for audit fields
Why:
- Permission classes prevent unauthorized access
- Explicit fields prevent accidental data exposure
- Read-only fields prevent tampering with audit trail
Complete Secure Endpoint Example
import users.models as _users_models
import users.serializers as _users_serializers
from rest_framework import viewsets, status
from rest_framework.decorators import action
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.throttling import UserRateThrottle
class UserViewSet(viewsets.ModelViewSet):
"""
User management API.
Security:
- Requires authentication
- Users can only access their own data (see get_queryset)
- Rate limited for sensitive actions
- Input validation via serializer
"""
queryset = _users_models.User.objects.all()
serializer_class = _users_serializers.UserSerializer
permission_classes = [IsAuthenticated]
def get_queryset(self):
"""Users can only see active, non-deleted records."""
qs = super().get_queryset().filter(is_deleted=False)
if not self.request.user.is_staff:
qs = qs.filter(id=self.request.user.id)
return qs
def perform_create(self, serializer):
"""Auto-set created_by on creation."""
serializer.save(created_by=self.request.user)
@action(
detail=False,
methods=['post'],
throttle_classes=[UserRateThrottle],
permission_classes=[IsAuthenticated]
)
def change_password(self, request):
"""Change user password (rate limited)."""
serializer = _serializers.ChangePasswordSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = request.user
user.set_password(serializer.validated_data['new_password'])
user.save()
return Response({'status': 'password changed'})
Security Checklist by Endpoint Type
Read-Only Endpoints (GET)
class PublicArticleViewSet(viewsets.ReadOnlyModelViewSet):
"""Public articles (read-only)."""
queryset = Article.objects.filter(is_published=True, is_deleted=False)
serializer_class = ArticleSerializer
permission_classes = [AllowAny]
throttle_classes = [AnonRateThrottle]
Create Endpoints (POST)
class UserCreateView(generics.CreateAPIView):
"""User registration (public)."""
serializer_class = UserCreateSerializer
permission_classes = [AllowAny]
throttle_classes = [AnonRateThrottle]
def perform_create(self, serializer):
"""
Create user and send verification email.
Security: Rate limited, email validation in serializer.
"""
user = serializer.save()
send_verification_email(user)
Update Endpoints (PUT/PATCH)
class UserUpdateView(generics.UpdateAPIView):
"""Update user profile."""
serializer_class = UserUpdateSerializer
permission_classes = [IsAuthenticated, IsOwner]
def get_object(self):
"""Users can only update their own profile."""
return self.request.user
Delete Endpoints (DELETE)
class UserDestroyView(generics.DestroyAPIView):
"""Soft delete user account."""
permission_classes = [IsAuthenticated, IsOwner]
def perform_destroy(self, instance):
"""Soft delete instead of hard delete."""
instance.is_deleted = True
instance.save(update_fields=['is_deleted', 'updated_at'])
Common Security Violations
Violation 1: No Permission Classes
class AdminViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
Auto-fix:
class AdminViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
permission_classes = [IsAuthenticated, IsAdminUser]
Violation 2: Serializer Exposes Sensitive Data
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
I auto-fix:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ['id', 'email', 'first_name', 'last_name', 'created_at']
read_only_fields = ['id', 'created_at', 'updated_at']
Violation 3: Raw SQL Injection Risk
def get_user(user_id):
query = f"SELECT * FROM users WHERE id = {user_id}"
return User.objects.raw(query)
I auto-fix:
def get_user(user_id):
return User.objects.filter(id=user_id).first()
Violation 4: No Rate Limiting on Auth
class LoginView(APIView):
def post(self, request):
pass
I auto-fix:
from rest_framework.throttling import AnonRateThrottle
class LoginView(APIView):
throttle_classes = [AnonRateThrottle]
def post(self, request):
pass
OWASP Top 10 Coverage
Automatically check for:
- Broken Access Control → Permission classes required
- Cryptographic Failures → No sensitive fields in serializers
- Injection → No raw SQL, use ORM
- Insecure Design → Enforce secure-by-default patterns
- Security Misconfiguration → CORS, HTTPS, rate limiting
- Vulnerable Components → (Check in CI/CD)
- Authentication Failures → Throttling on auth endpoints
- Data Integrity Failures → Serializer validation required
- Logging Failures → (Suggest logging for sensitive ops)
- SSRF → Validate URLs in serializers
Custom Permission Classes
Recognize and allow custom permissions:
from rest_framework.permissions import BasePermission
class IsOwner(BasePermission):
"""User can only access their own objects."""
def has_object_permission(self, request, view, obj):
return obj.user == request.user
class ProfileViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, IsOwner]
Integration with Other Skills
Works together with:
- model-entity-validator: Ensures models don't expose sensitive fields
- test-coverage-advisor: Suggests security tests
- django-reviewer: Full security audit during review
Success Criteria
✅ ALL endpoints have permission classes
✅ ALL serializers use explicit field lists
✅ NO raw SQL queries
✅ Rate limiting on sensitive endpoints
✅ NO sensitive fields exposed
✅ Input validation on all write operations
✅ Developer understands OWASP risks
Behavior
Proactive enforcement:
- Check security without being asked
- Add permission classes automatically
- Fix serializer field exposure immediately
- Explain WHY each security measure is critical
- Reference OWASP categories
Never:
- Require explicit "check security" request
- Wait for pen test results
- Just warn without fixing
- Allow endpoints without security
Block completion if:
- No permission classes defined
- Serializer uses
fields = '__all__'
- Raw SQL detected
- Sensitive endpoints lack rate limiting