Skip to main content

Django scanner

shadowaudit's Django scanner reads urls.py files and extracts route definitions from path(), re_path(), url(), and router.register() calls.

How it works​

The scanner finds all urls.py and *_urls.py files in the scanned directory and uses regex to extract route patterns. It converts Django's <type:param> syntax to OpenAPI's {param} syntax for comparison.

Detected patterns​

Pattern 1 — path() function​

from django.urls import path
from . import views

urlpatterns = [
path('users/', views.UserList),
path('users/<int:pk>/', views.UserDetail),
]

Django path parameters are converted to OpenAPI syntax:

DjangoOpenAPI (stored)
<int:pk>{pk}
<str:name>{name}
<slug:slug>{slug}
<uuid:id>{id}

Pattern 2 — re_path() / url() with regex​

from django.urls import re_path

urlpatterns = [
re_path(r'^api/items/$', views.ItemList),
]
# → GET /api/items/ (regex anchors stripped)

Pattern 3 — DRF router.register()​

from rest_framework.routers import DefaultRouter
from .views import ArticleViewSet

router = DefaultRouter()
router.register(r'articles', ArticleViewSet)

This generates 6 standard REST routes automatically:

GET /articles/ — list
POST /articles/ — create
GET /articles/{pk}/ — retrieve
PUT /articles/{pk}/ — update
PATCH /articles/{pk}/ — partial_update
DELETE /articles/{pk}/ — destroy

Example scan output​

shadowaudit --dir ./src --spec openapi.json --framework django
[INFO] Running Django route scanner...
[✓] Found 12 Django routes in ./src
[INFO] GET /articles/ → urls.py:7 [no auth]
[INFO] POST /articles/ → urls.py:7 [no auth]
[INFO] GET /articles/{pk}/ → urls.py:7 [no auth]
[INFO] GET /health/ → urls.py:10 [no auth]
[INFO] GET /api/users/ → urls.py:12 [auth]

Auth detection​

shadowaudit detects Django auth by scanning the urls.py file content for hardcoded patterns:

@login_required @permission_required IsAuthenticated IsAdminUser
TokenAuthentication SessionAuthentication permission_classes authentication_classes
LoginRequiredMixin PermissionRequiredMixin jwt_required requires_auth

Known limitations​

  • Cross-file CBV detection — class-based views defined in views.py and registered in urls.py use the v0.3.1 cross-file auth detection path. Same-file CBV detection (class + router.register() / .as_view() in same file) is fully supported.
  • include('app.urls') prefixes — reconciled since v0.3.1 ✅
  • Class-based view methods — fully supported since v0.6.0 ✅ (ModelViewSet, APIView, DRF generics)
  • Cross-file auth detection — supported since v0.3.1 ✅ (scans views.py for permission_classes, @login_required, etc.)

Class-based views (v0.6.0+)​

shadowaudit detects Django REST Framework class-based views:

ModelViewSet​

class UserViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated]

router = DefaultRouter()
router.register(r'users', UserViewSet)

Generates all 6 CRUD routes: GET/POST /users/, GET/PUT/PATCH/DELETE /users/{pk}/

APIView​

class UserListView(APIView):
permission_classes = [IsAuthenticated]
def get(self, request): pass
def post(self, request): pass

urlpatterns = [path('users/', UserListView.as_view())]

Methods detected from def get, def post, etc. in the class body.

DRF generic views​

ClassMethods
ListAPIViewGET
CreateAPIViewPOST
ListCreateAPIViewGET + POST
RetrieveAPIViewGET
UpdateAPIViewPUT + PATCH
DestroyAPIViewDELETE
RetrieveUpdateDestroyAPIViewGET + PUT + PATCH + DELETE

Auth detection for CBVs​

  • permission_classes = [IsAuthenticated] → hasAuth: true
  • permission_classes = [AllowAny] → hasAuth: false
  • authentication_classes = [...] → hasAuth: true
  • No permission_classes → hasAuth: false