Fix cards for Python and Django
A01:2025 Broken Access Control
- Cross-site request forgery (CSRF)CWE-352
Keep django.middleware.csrf.CsrfViewMiddleware in MIDDLEWARE and do not exempt views: it checks the Origin (or, over HTTPS, the Referer) against the site and CSRF_TRUSTED_ORIGINS, and the token.
- Missing object-level authorization (IDOR and BOLA)CWE-862 · CWE-639 · CWE-863
Build one queryset that holds the rule, from request.user and its tenant and role, never from request data: Invoice.objects.filter(tenant=member.tenant), narrowed to owner=user for members.
- Open redirectCWE-601
Prefer Django's LoginView, which checks next itself; add other hosts with success_url_allowed_hosts.
- Path traversalCWE-22 · CWE-23 · CWE-36 · CWE-59
Resolve both paths and compare whole components: base = Path(settings.FILES_ROOT).resolve(), target = (base / name).resolve(), and return FileResponse(target.open('rb')) only if target.is_relative_to(base). resolve() collapses ../ and follows symlinks; the check also catches absolute names.
- Server-side request forgery (SSRF)CWE-918
Attach the policy to the connection, not the URL string. Allow only http/https and ports 80/443 and reject credentials; resolve the host (or read an IP literal in any notation) and reject loopback, private, link-local and IPv4-mapped addresses with ipaddress; connect to the vetted IP; re-validate every redirect; cap time and bytes.
A04:2025 Cryptographic Failures
- Weak password hashingCWE-916 · CWE-759 · CWE-327
Install argon2-cffi and list django.contrib.auth.hashers.Argon2PasswordHasher first in PASSWORD_HASHERS, keeping older hashers after it.
A05:2025 Injection
- Cross-site scripting (XSS)CWE-79
Let Django's autoescaping work: render {{ value }} without |safe, mark_safe() or {% autoescape off %}, so < > ' " and & become entities in text and quoted attributes.
- CSV formula injectionCWE-1236
User text in a CSV is attacker-controlled. If a cell starts with = + - @, a tab, CR, LF or NUL, prefix it with an apostrophe so a spreadsheet treats it as text, then write it with csv.writer, which escapes commas, quotes and newlines.
- SQL injectionCWE-89
Let the ORM build the query: Order.objects.filter(customer__name=name, status=status) sends both values as bound parameters.
A06:2025 Insecure Design
- ClickjackingCWE-1021
Keep Django's XFrameOptionsMiddleware (X-Frame-Options defaults to DENY) and add a CSP with ContentSecurityPolicyMiddleware and SECURE_CSP = {"frame-ancestors": [CSP.NONE]}, which browsers honor over X-Frame-Options.
- Unrestricted file uploadCWE-434 · CWE-646 · CWE-73 · CWE-770
Decide the type from the bytes and never let the client pick the name.
A07:2025 Authentication Failures
- Missing authentication for a critical functionCWE-306
Authenticate the operator, do not trust the network or a client header.
A08:2025 Software or Data Integrity Failures
- Mass assignment (over-posting)CWE-915
Give every ModelForm an explicit allowlist, fields = ["display_name", "bio"]: it binds only those fields and ignores the rest of the request.
Not covered in Django yet
These weaknesses have fix cards in other stacks but not in this one.
- CORS misconfiguration on credentialed APIs
- XML external entity (XXE) processing
- Insecure randomness for security tokens
- Code injection
- LDAP filter injection
- NoSQL operator injection
- OS command and argument injection
- Server-side template injection
- XPath injection
- Regular-expression denial of service (ReDoS)
- Unbounded resource consumption
- Hard-coded secrets
- Improper TLS certificate validation
- JWT validation in APIs (bearer tokens)
- Session fixation and session cookies
- Insecure deserialization
- CRLF injection in logs and response headers
Supported versions
Each feature release gets mainstream support until the next one and security fixes for about eight months after; LTS releases get security fixes for three years. Django 2028 and later each get three years. Django support policy.
| Version | Latest | Status | Released | End of support |
|---|---|---|---|---|
| 6.1 | 6.1.2 | SupportedMainstream support until April 2027; the cards use it. | 5 August 2026 | December 2027 |
| 6.0 | 6.0.9 | Security fixes only | 3 December 2025 | April 2027 |
| 5.2 (LTS) | 5.2.18 | Security fixes only | 2 April 2025 | April 2028 |
| Version | Latest | Status | Released | End of support |
|---|---|---|---|---|
| 3.15 | 3.15.0 | Supported | 9 October 2026 | October 2031 |
| 3.14 | 3.14.8 | Supported | 7 October 2025 | October 2030 |
| 3.13 | 3.13.16 | Security fixes only | 7 October 2024 | October 2029 |
| 3.12 | 3.12.15 | Security fixes only | 2 October 2023 | October 2028 |
Django 6.1 needs Python 3.12 or later. CPython release schedule
Minimum secure versions
The lowest release in each supported line that fixes every published advisory listed here. Anything older is missing a security fix.
| Package | At least | Why |
|---|---|---|
django | 6.1.2, 6.0.9 or 5.2.18 | The 6 October 2026 security releases: CVE-2026-84429 (quadratic header parsing), CVE-2026-77050 (language-code denial of service), CVE-2026-87890 (request forgery through spatial lookup bytes) and CVE-2026-87975 (privilege abuse in model formsets with editable primary keys). Advisory |
CPython | 3.15.0, 3.14.8, 3.13.16 or 3.12.15 | Six CVEs fixed on 30 September and 1 October 2026, such as an ssl use-after-free (CVE-2026-19445), a tarfile filter bypass (CVE-2026-19672) and unbounded zipfile decompression (CVE-2026-15310); 3.11 to 3.13 also fix two more tarfile CVEs, and the fix for one of them, CVE-2026-87910, was in no 3.14 release on 11 October 2026. Advisory |
Middleware and security order
SecurityMiddleware: Near the top ofMIDDLEWARE, so an HTTPS redirect happens before other middleware runs.SessionMiddleware: Before any middleware that uses the session; afterUpdateCacheMiddlewareif you cache.CommonMiddleware: ThenCsrfViewMiddleware, before any view middleware that assumes CSRF attacks have been dealt with.AuthenticationMiddleware: AfterSessionMiddleware, which it uses forrequest.user; thenMessageMiddleware.XFrameOptionsMiddlewareandContentSecurityPolicyMiddleware: Frame protection, and CSP from theSECURE_CSPsetting (new in Django 6.0).- Views:
login_requiredorLoginRequiredMiddleware, plus querysets filtered by the owner.
Secure defaults and settings
| Setting | Value | Why |
|---|---|---|
DEBUG | False (but startproject sets True) | Never True in production. |
SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE | False | Set True. |
SECURE_HSTS_SECONDS, SECURE_SSL_REDIRECT | 0, False | Set HSTS to at least a year once HTTPS works everywhere. |
SECURE_CONTENT_TYPE_NOSNIFF, X_FRAME_OPTIONS, SECURE_REFERRER_POLICY, SECURE_CROSS_ORIGIN_OPENER_POLICY | True, 'DENY', 'same-origin', 'same-origin' | Keep them. |
SESSION_COOKIE_SAMESITE | 'Lax' | Keep it. |
PASSWORD_HASHERS | PBKDF2-SHA256 first, 1,500,000 iterations | Put Argon2PasswordHasher first if argon2-cffi is installed; old hashes upgrade at sign-in. |
SECURE_CSP | Empty | Set a policy and add ContentSecurityPolicyMiddleware. |
Escape hatches to search for
Framework APIs that switch a protection off. Each one is sometimes needed; search for them in review and make each use justify itself.
| API | Risk | Instead |
|---|---|---|
mark_safe(), |safe, {% autoescape off %} | Turns off escaping. | format_html() for markup around escaped values. |
@csrf_exempt | Removes CSRF protection from a view. | Keep CsrfViewMiddleware and set CSRF_TRUSTED_ORIGINS for real cross-origin needs. |
raw(), extra(), RawSQL, cursor.execute() with formatting | SQL injection. | The ORM, or parameters passed separately. |
ALLOWED_HOSTS = ['*'] | Host header poisoning in generated links such as password-reset emails. | Your host names. |
ModelForm with fields = '__all__' | Mass assignment of fields added later. | An explicit fields list. |
HttpResponseRedirect(request.GET['next']) | Open redirect. | url_has_allowed_host_and_scheme() with require_https. |
Checklist, mapped to ASVS 5.0.0
- v5.0.0-1.2.1 Templates rely on autoescaping; no request data reaches
mark_safeor|safe. - v5.0.0-3.5.1
CsrfViewMiddlewareis on and no cookie-authenticated view is@csrf_exempt. - v5.0.0-8.2.2 Views load objects through querysets filtered by
request.user. - v5.0.0-1.2.4 No SQL is built with string formatting.
- v5.0.0-15.3.3 Forms list their fields explicitly.
- v5.0.0-3.4.1
SECURE_HSTS_SECONDSis at least 31536000. - v5.0.0-3.3.1 Session and CSRF cookies are
Secure. - v5.0.0-13.4.2
DEBUGis off andmanage.py check --deployreports no warnings you have not accepted. - v5.0.0-11.4.2 Passwords use Django's hashers with Argon2 or PBKDF2 at the current iteration count.