| Input Validation |
TG-INPUT-001, TG-INPUT-002 |
forms.Form, ModelForm, custom clean_<field>() methods |
Serializer fields, custom field validators |
Pydantic v2 BaseModel, Field(..., min_length=1) |
WTForms, Marshmallow, or Pydantic request models |
Explicit schema boundary before DB persistence |
| Authentication |
TG-AUTH-001, TG-AUTH-002, TG-AUTH-004 |
django.contrib.auth, session middleware, @login_required |
authentication_classes = [JWTAuthentication, SessionAuthentication] |
Depends(get_current_user), OAuth2 password/bearer |
flask_login, @login_required, session cookies |
N/A (Data layer only) |
| Authorization & Ownership |
TG-AUTH-003, TG-AUTH-007 |
UserPassesTestMixin, @user_passes_test, queryset .filter(user=request.user) |
permission_classes, custom BasePermission, has_object_permission |
SecurityScopes, dependency checks, scoped database queries |
Route decorators, session-based ownership validation |
Model query scoping (filter_by(user_id=current_user.id)) |
| CSRF Protection |
TG-CSRF-001, TG-CSRF-002 |
CsrfViewMiddleware, {% csrf_token %}, CSRF_COOKIE_SECURE |
Relevant for session-authenticated API views with cookie auth |
Relevant for cookie-authenticated browser API endpoints |
flask_wtf.CSRFProtect, SameSite=Strict cookies |
N/A |
| Mass Assignment |
TG-AUTH-006 |
Explicit fields = [...] in ModelForm (never fields = '__all__') |
Serializer read_only_fields, explicit writable fields |
Explicit request schemas (never unpack User(**req.dict())) |
Schema allowlists (never unpack raw request.json to model) |
Explicit column assignments or .pick() updates |
| Rate Limiting & DoS |
TG-RATE-001, TG-RATE-002, TG-RATE-003 |
django-ratelimit, reverse-proxy (Nginx/Cloudflare) |
AnonRateThrottle, UserRateThrottle, ScopedRateThrottle |
slowapi (Limiter), Redis token-bucket middleware |
Flask-Limiter, Redis-backed rate limiting |
Connection pooling limits (pool_size, max_overflow) |
| ORM & Database Safety |
TG-DB-001, TG-INPUT-003 |
Parameterized ORM queries (avoid raw SQL string concatenation) |
Queryset scoping and parameterized ORM lookups |
SQLAlchemy parameterized text(:param) bindings |
Parameterized queries via Flask-SQLAlchemy |
Bound parameters, avoid text(f"SELECT ... {input}") |
| File Upload Safety |
TG-INPUT-004 |
FileField(upload_to=...), file extension/MIME/size validation |
MultiPartParser, serializer file validation |
UploadFile, byte size streaming caps, content-type checks |
secure_filename(file.filename), size caps, MIME sniff |
Store in isolated object storage (S3), never execute |
| SSRF & Outbound Requests |
TG-SSRF-001, TG-SSRF-002, TG-SSRF-004 |
Validate external URLs before requests.get() or urllib |
Validate external webhook / callback URLs |
Validate destination with httpx or requests, block private IPs |
Validate user URLs before requests.get(), block 127.0.0.1/169.254 |
N/A |
| Dependency & Supply Chain |
TG-SUPPLY-001 … TG-SUPPLY-005 |
requirements.txt, pip-audit, lockfiles (poetry.lock, uv.lock) |
requirements.txt, pip-audit, lockfiles |
pyproject.toml, pip-audit, pinned dependencies |
requirements.txt, pip-audit, lockfiles |
Pin driver versions (e.g. asyncpg, psycopg2-binary) |
| Cache & Sensitive Responses |
TG-CACHE-001, TG-CACHE-002 |
@never_cache, Cache-Control: no-store on user profile views |
Cache-Control: no-store, private on sensitive endpoints |
Response headers Cache-Control: no-store on private routes |
@after_request header injection (no-store) |
Avoid caching sensitive query results across users |