Loading...
Loading...
Django access control and IDOR security review. Use when reviewing Django views, DRF viewsets, ORM queries, or any Python/Django code handling user authorization. Trigger keywords: "IDOR", "access control", "authorization", "Django permissions", "object permissions", "tenant isolation", "broken access".
npx skill4agent add getsentry/skills django-access-review□ Where are permission checks implemented?
- Decorators? (@login_required, @permission_required, custom?)
- Middleware? (TenantMiddleware, AuthorizationMiddleware?)
- Base classes? (BaseAPIView, TenantScopedViewSet?)
- Permission classes? (DRF permission_classes?)
- Custom mixins? (OwnershipMixin, TenantMixin?)
□ How are queries scoped?
- Custom managers? (TenantManager, UserScopedManager?)
- get_queryset() overrides?
- Middleware that sets query context?
□ What's the ownership model?
- Single user ownership? (document.owner_id)
- Organization/tenant ownership? (document.organization_id)
- Hierarchical? (org -> team -> user -> resource)
- Role-based within context? (org admin vs member)# Find how auth is typically done
grep -rn "permission_classes\|@login_required\|@permission_required" --include="*.py" | head -20
# Find base classes that views inherit from
grep -rn "class Base.*View\|class.*Mixin.*:" --include="*.py" | head -20
# Find custom managers
grep -rn "class.*Manager\|def get_queryset" --include="*.py" | head -20
# Find ownership fields on models
grep -rn "owner\|user_id\|organization\|tenant" --include="models.py" | head -30□ What models contain user data?
□ Which have ownership fields (owner_id, user_id, organization_id)?
□ Which are accessed via ID in URLs or request bodies?1. Where does the resource ID enter the system?
- URL path: /api/documents/{id}/
- Query param: ?document_id=123
- Request body: {"document_id": 123}
2. Where is that ID used to fetch data?
- Find the ORM query or database call
3. Between (1) and (2), what checks exist?
- Is the query scoped to current user?
- Is there an explicit ownership check?
- Is there a permission check on the object?
- Does a base class or mixin enforce access?
4. If you can't find a check, is there one you missed?
- Check parent classes
- Check middleware
- Check managers
- Check decorators at URL level□ For list endpoints: Does the query filter to user's data, or return everything?
□ For create endpoints: Who sets the owner - the server or the request?
□ For bulk operations: Are they scoped to user's data?
□ For related resources: If I can access a document, can I access its comments?
What if the document belongs to someone else?
□ For tenant/org resources: Can User in Org A access Org B's data by changing
the org_id in the URL?Endpoint: GET /api/documents/{pk}/
1. Find the view handling this URL
→ DocumentViewSet.retrieve() in api/views.py
2. Check what DocumentViewSet inherits from
→ class DocumentViewSet(viewsets.ModelViewSet)
→ No custom base class with authorization
3. Check permission_classes
→ permission_classes = [IsAuthenticated]
→ Only checks login, not ownership
4. Check get_queryset()
→ def get_queryset(self):
→ return Document.objects.all()
→ Returns ALL documents!
5. Check for has_object_permission()
→ Not implemented
6. Check retrieve() method
→ Uses default, which calls get_object()
→ get_object() uses get_queryset(), which returns all
7. Conclusion: IDOR - Any authenticated user can access any documentPotential gap indicators (investigate further, don't auto-flag):
- get_queryset() returns .all() or filters without user
- Direct Model.objects.get(pk=pk) without ownership in query
- ID comes from request body for sensitive operations
- Permission class checks auth but not ownership
- No has_object_permission() and queryset isn't scoped
Likely safe patterns (but verify the implementation):
- get_queryset() filters by request.user or user's org
- Custom permission class with has_object_permission()
- Base class that enforces scoping
- Manager that auto-filters| Level | Meaning | Action |
|---|---|---|
| HIGH | Traced the flow, confirmed no check exists | Report with evidence |
| MEDIUM | Check may exist but couldn't confirm | Note for manual verification |
| LOW | Theoretical, likely mitigated | Do not report |
def get_resource(resource_id):
# IMPORTANT: Caller must ensure user has access to this resource
return Resource.objects.get(pk=resource_id)def get_resource(resource_id, user):
resource = Resource.objects.get(pk=resource_id)
if resource.owner_id != user.id:
raise PermissionDenied("Access denied")
return resource## Access Control Review: [Component]
### Authorization Model
[Brief description of how this codebase handles authorization]
### Findings
#### [IDOR-001] [Title] (Severity: High/Medium)
- **Location**: `path/to/file.py:123`
- **Confidence**: High - confirmed through code tracing
- **The Question**: Can User A access User B's documents?
- **Investigation**:
1. Traced GET /api/documents/{pk}/ to DocumentViewSet
2. Checked get_queryset() - returns Document.objects.all()
3. Checked permission_classes - only IsAuthenticated
4. Checked for has_object_permission() - not implemented
5. Verified no relevant middleware or base class checks
- **Evidence**: [Code snippet showing the gap]
- **Impact**: Any authenticated user can read any document by ID
- **Suggested Fix**: [Code that enforces authorization - NOT a comment]
### Needs Manual Verification
[Issues where authorization exists but couldn't confirm effectiveness]
### Areas Not Reviewed
[Endpoints or flows not covered in this review]# Scoped to user
Document.objects.filter(owner=request.user)
# Scoped to organization
Document.objects.filter(organization=request.user.organization)
# Using a custom manager
Document.objects.for_user(request.user) # Investigate what this does# DRF permission classes
permission_classes = [IsAuthenticated, IsOwner]
# Custom has_object_permission
def has_object_permission(self, request, view, obj):
return obj.owner == request.user
# Django decorators
@permission_required('app.view_document')
# Manual checks
if document.owner != request.user:
raise PermissionDenied()# Server-side (safe)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
# From request (investigate)
serializer.save(**request.data) # Does request.data include owner?□ I understand how authorization is typically implemented in this codebase
□ I've identified the ownership model (user, org, tenant, etc.)
□ I've mapped the key endpoints that handle user data
□ For each sensitive endpoint, I've traced the flow and asked:
- Where does the ID come from?
- Where is data fetched?
- What checks exist between input and data access?
□ I've verified my findings by checking parent classes and middleware
□ I've only reported issues I've confirmed through investigation