Join our Newsletter — 33% off our NHI Course
Home› Guides› Django Security Guide: Settings, ORM Safety and Deployment…
Guide Application Security

Django Security Guide: Settings, ORM Safety and Deployment Checks

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 26 October 2026 14 min read
On this page

Django protects against most common web attacks out of the box, but a single production setting such as DEBUG = True, a wildcard ALLOWED_HOSTS or a leaked SECRET_KEY can undo that protection for the whole site. Many Django incidents trace back to settings, to code that leaves the ORM for raw SQL or unsafe dictionary expansion, or to forms that expose more model fields than intended. This guide covers the settings that matter in production and how manage.py check --deploy enforces them, safe ORM and template patterns, sessions and CSRF, secret key handling, file uploads and the new built-in Content Security Policy. It is for Django developers, application security engineers and platform teams running Django services, and it complements the language-level Python Security Guide rather than repeating it.

Key takeaways

  • Run check --deploy against production settings. It flags missing SecurityMiddleware, weak SECRET_KEY values, DEBUG, empty ALLOWED_HOSTS, HSTS, cookie and CSP problems by check ID, and it belongs in CI as a failing gate.
  • The ORM is safe until you step outside it. Querysets are parameterised; raw(), extra(), RawSQL, cursor.execute with string formatting and **request.GET expansion into filter() are where injection returns, as CVE-2025-64459 showed.
  • SECRET_KEY signs everything that matters. Sessions, password reset tokens, messages and signed cookies depend on it; load it from the environment or a secrets manager and rotate with SECRET_KEY_FALLBACKS.
  • Explicit fields stop mass assignment. Django's own documentation recommends listing fields on every ModelForm and calls "__all__" and exclude much less secure.
  • Some protection is not in Django at all. Login throttling, CORS, upload size limits at the web server and object-level authorisation are your responsibility.

Built-in protections and their limits

The Django security overview is explicit that its defaults suit general-purpose sites and that special cases need attention. The table lists each built-in protection, the component that provides it and the documented way it fails.

ProtectionProvided byDefaultDocumented limit or bypass
XSSTemplate autoescapingOn|safe, mark_safe, is_safe custom filters, {% autoescape off %}, unquoted attributes such as class={{ var }}, non-HTML output
CSRFCsrfViewMiddleware, {% csrf_token %}On in the default MIDDLEWARE@csrf_exempt, untrusted subdomains, state changes on GET
SQL injectionQuerySet parameterisationOnraw(), extra(), RawSQL, formatted cursor.execute strings, quoted %s placeholders
ClickjackingXFrameOptionsMiddlewareX_FRAME_OPTIONS = 'DENY'Per-view exemptions, middleware removed
Host headerHttpRequest.get_host() with ALLOWED_HOSTSEnforced when DEBUG = FalseReading request.META["HTTP_HOST"] directly, wildcard hosts, careless USE_X_FORWARDED_HOST
Passwordsdjango.contrib.auth hashersPBKDF2 with SHA256Custom auth that stores or compares passwords itself
Security headersSecurityMiddlewarenosniff on, Referrer-Policy: same-origin, Cross-Origin-Opener-Policy: same-origin; HSTS and SSL redirect offMiddleware missing (check security.W001)
Content Security PolicyContentSecurityPolicyMiddleware (Django 6.0 and later)Off until SECURE_CSP is setExcluded paths weaken the whole site

Django does not throttle authentication attempts and does not ship CORS support. Add a throttling package or web server rule for login, password reset and API token endpoints, and use a maintained CORS middleware with an explicit origin list. See the OWASP Top 10 Guide for the wider vulnerability classes.

Production settings and deployment checks

python manage.py check --deploy, run with the production settings module, turns the deployment checklist into machine-checkable warnings. The table maps the settings that matter to their check IDs from django/core/checks/security.

SettingProduction valueCheck IDNotes
DEBUGFalsesecurity.W018True leaks source excerpts, local variables and settings in error pages
SECRET_KEYLong random value from the environment or a filesecurity.W009Warns under 50 characters, under 5 unique characters or with the django-insecure- prefix; W025 applies the same test to fallbacks
ALLOWED_HOSTSExplicit host namessecurity.W020Also reject unknown hosts at the proxy, for example an nginx default server returning 444
MIDDLEWAREIncludes SecurityMiddleware and XFrameOptionsMiddlewaresecurity.W001, W002Without SecurityMiddleware the HSTS, nosniff, referrer, COOP and redirect settings do nothing
SECURE_SSL_REDIRECTTrue, or redirect at the proxysecurity.W008Behind a proxy, set SECURE_PROXY_SSL_HEADER only if the proxy strips and sets the header itself
SECURE_HSTS_SECONDSStart small (for example 3600), then raise to a year or moresecurity.W004Django warns that careless HSTS can cause serious, irreversible problems
SECURE_HSTS_INCLUDE_SUBDOMAINS, SECURE_HSTS_PRELOADTrue once every subdomain is HTTPS-onlysecurity.W005, W021Preload is hard to undo; treat it as a one-way decision
SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURETruesecurity.W012, W016Stops cookies leaking over an initial HTTP request
X_FRAME_OPTIONS'DENY' unless framing is neededsecurity.W019Use per-view decorators for the few pages that must be framed
SECURE_REFERRER_POLICY, SECURE_CROSS_ORIGIN_OPENER_POLICYDefaults same-originsecurity.W022, E023, E024Invalid values are errors, not warnings
SECURE_CSPDictionary policy, nonce-basedsecurity.E026, W027W027 fires when CSP.NONCE is used without the csp context processor

The checklist also says to replace runserver with a production WSGI or ASGI server, to restrict cache servers and databases to the application hosts, to keep Python code outside the web server's document root, and to provide custom 404.html, 500.html, 403.html and 400.html templates.

ORM and query safety

Django querysets separate SQL from parameters and let the database driver handle escaping. The risk comes from the escape hatches and from passing user-controlled structure, not just values, into the ORM.

PatternRiskSafe alternative
Person.objects.raw("... WHERE last_name = '%s'" % name)SQL injection through string formattingraw("... WHERE last_name = %s", [name]) with an unquoted placeholder
cursor.execute(f"UPDATE ... WHERE id = {x}")SQL injectioncursor.execute("UPDATE ... WHERE id = %s", [x])
queryset.extra(where=[user_input]), RawSQL(user_input)Raw fragments in otherwise safe queriesQuery expressions (F, Q, Func), or parameters passed separately
Model.objects.filter(**request.GET.dict())Attacker chooses lookups, relations and internal arguments; CVE-2025-64459 allowed SQL injection through a crafted _connector key in filter(), exclude(), get() and Q()Map an allow-list of query parameters to fixed lookups, or use a filtering library with declared fields
order_by(request.GET["sort"])Sorting by unintended fields, including related sensitive fieldsAllow-list of sort keys mapped to field names
Model.objects.get(pk=pk) without ownership checkInsecure direct object referenceget_object_or_404(Model, pk=pk, owner=request.user) or queryset scoping in get_queryset()

The Django project rated CVE-2025-64459 high severity and fixed it in 5.2.8, 5.1.14 and 4.2.26. The lesson outlasts the patch: dictionary expansion lets the request decide the shape of a query. See the SQL Injection Prevention Guide.

Forms, serializers and mass assignment

Django's ModelForm documentation strongly recommends setting fields explicitly and describes fields = "__all__" and exclude as much less secure, because a field added to the model later becomes editable without anyone reviewing the form. It cites the 2012 GitHub public key incident as an example of this class of flaw. Apply the same rule to Django REST framework serializers and to any view that calls Model(**data) or update(**data). Validation in clean() methods and validators is the place for input rules; see the Input Validation and Output Encoding Guide.

Templates and XSS

  • Quote every attribute: the Django security docs show that <style class={{ var }}> can be abused even with autoescaping, while class="{{ var }}" cannot.
  • Treat safe markers as sinks: review every |safe, mark_safe(), format_html misuse and {% autoescape off %}. Prefer format_html(), which escapes its arguments, over building HTML with string concatenation and mark_safe.
  • Stored HTML: if users may submit rich text, sanitise it with an allow-list sanitiser on input and output, as the Django docs advise.
  • JavaScript contexts: pass data to scripts with the json_script filter rather than rendering variables inside <script> blocks, because HTML escaping is the wrong encoding for JavaScript.

See the Cross-Site Scripting (XSS) Prevention Guide.

Sessions, authentication and CSRF

  • Cookie defaults: SESSION_COOKIE_HTTPONLY defaults to True, SESSION_COOKIE_SAMESITE to 'Lax' and SESSION_COOKIE_AGE to 1,209,600 seconds (two weeks). Shorten the age for sensitive applications and set SESSION_EXPIRE_AT_BROWSER_CLOSE where appropriate.
  • Session serialiser: the default is JSONSerializer. Do not switch to a pickle-based serialiser; with the signed_cookies backend a leaked SECRET_KEY would then become code execution. See the Secure Deserialisation Guide.
  • Session fixation: use django.contrib.auth.login(), which rotates the session key on login; custom login code that sets request.session["user_id"] directly does not. See the Session Fixation Guide.
  • Password hashing: the first entry in PASSWORD_HASHERS is used for new hashes; Django upgrades existing hashes on login. Keep AUTH_PASSWORD_VALIDATORS enabled.
  • CSRF: keep CsrfViewMiddleware, avoid @csrf_exempt on cookie-authenticated views, list cross-origin form posts in CSRF_TRUSTED_ORIGINS with full scheme, and remember that a subdomain controlled by someone else can set cookies for yours. See the CSRF Prevention Guide.
  • Admin: django.contrib.admin is a high-value target. Restrict it by network or SSO at the proxy, require MFA for staff accounts, and keep is_superuser to a small group.

Secrets and the SECRET_KEY

The deployment checklist says the production key must be large, random, unique to production and never committed. Load it with os.environ["SECRET_KEY"] or from a file readable only by the application user, and treat database passwords in DATABASES the same way. To rotate, move the current key into SECRET_KEY_FALLBACKS, deploy a new SECRET_KEY, and remove the old key from the fallbacks once sessions and reset tokens issued under it have expired. A key that has leaked should be rotated without a fallback, accepting that all users are signed out.

The Django application is a non-human identity in its own right: its database role, cache credentials, email provider key and cloud storage credentials should each be scoped to the service, owned and rotated. Exposed source trees are a frequent leak path; the misconfigured Git servers study found deployment credentials in hundreds of thousands of publicly served .git/config files, which is one reason the Django checklist asks you to keep code outside the web root. See the Secrets Management Guide.

File uploads, media and CSP

  • Size limits at the edge: Django advises limiting request body size in the web server rather than relying on DATA_UPLOAD_MAX_MEMORY_SIZE or FILE_UPLOAD_MAX_MEMORY_SIZE; file uploads are not limited by the former, and under ASGI the whole request may be spooled to disk before Django validates size.
  • Polyglot files: the docs warn that a file with a valid PNG header followed by HTML passes ImageField validation with Pillow and may be rendered as HTML. Serve MEDIA_URL from a separate registrable domain (a subdomain is not enough), allow-list extensions and set Content-Disposition for downloads.
  • No execution: make sure handlers such as PHP are never active for the media directory. See the Secure File Upload Handling Guide.
  • CSP: Django 6.0 added ContentSecurityPolicyMiddleware, the SECURE_CSP and SECURE_CSP_REPORT_ONLY settings and nonce support through the django.template.context_processors.csp context processor. Earlier versions need a third-party package. Start with SECURE_CSP_REPORT_ONLY, then enforce. See the Content Security Policy Guide.

How to implement: a phased rollout

  1. Inventory (week 1): list Django versions per service against the supported versions on djangoproject.com. When this guide was written 5.2 was the LTS series with extended support until April 2028, 6.0 until April 2027 and 6.1 until December 2027.
  2. Deploy checks (week 1): run manage.py check --deploy with production settings in CI, first as a report, and fix W018, W020, W009 and W001 immediately.
  3. Secrets (weeks 1 to 3): remove keys and passwords from settings files and Git history, load them at runtime and rotate anything that was exposed.
  4. Transport and cookies (weeks 2 to 4): enable SSL redirect, secure cookies and a short SECURE_HSTS_SECONDS in staging, then production; raise HSTS gradually. Roll back by lowering the value before raising it further, since browsers cache HSTS.
  5. Code patterns (weeks 3 to 8): add Bandit or Semgrep rules for raw, extra, RawSQL, mark_safe, csrf_exempt, fields = "__all__" and **request.GET, triage, then make new findings blocking.
  6. CSP and gating (ongoing): report-only CSP for a release cycle, then enforce; make check --deploy --fail-level WARNING a merge gate once the backlog is clear.

A small team with one Django service can do steps 1 to 4 in a sprint. Larger estates benefit from a shared settings base module and a cookiecutter or template so new services start compliant.

Detection and measurement

  • Log signals: DisallowedHost errors from django.security.DisallowedHost (Host header probing), CSRF verification failures, SuspiciousOperation events, spikes in failed logins and database errors containing quote characters.
  • Error reporting: route django.security loggers to your SIEM rather than to email, and scrub sensitive POST data with @sensitive_post_parameters and @sensitive_variables.
  • Metrics: services passing check --deploy with zero warnings; services on a Django series with security support; median days from a Django security release to production; open raw SQL and mark_safe findings; ModelForms using __all__ or exclude; services with an enforced CSP.

Framework mapping

Django control areaNIST SP 800-53 Rev. 5NIST CSF 2.0OWASP ASVS 5.0MITRE ATT&CK addressed
ORM safety, explicit form fieldsSI-10PR.PS-06V1.2.4, V15.3.3T1190 Exploit Public-Facing Application
Sessions and cookiesSC-23PR.AA-05V3.3.1T1539 Steal Web Session Cookie
HTTPS, HSTS, headersSC-8PR.DS-02V3.4.1, V3.4.4
SECRET_KEY and credentialsSC-12, SC-28PR.DS-01V13.3.1T1552.001 Credentials In Files
DEBUG off, deploy checksSA-15, SA-11PR.PS-06V13.4.2
Supported versions and patchingRA-5, SR-3ID.RA-01V15.2.1T1195.002 Compromise Software Supply Chain

In CIS Controls v8.1 this sits in Control 16 (Application Software Security); in ISO/IEC 27001:2022 Annex A in 8.25 (secure development life cycle), 8.28 (secure coding) and 8.9 (configuration management).

Common failure patterns

  • DEBUG in production: a debug error page reveals settings, paths and local variables, often including connection strings.
  • Wildcard hosts: ALLOWED_HOSTS = ["*"] added to silence a load balancer health check enables poisoned password reset links.
  • Development key in production: the generated django-insecure- key, or a key shared across environments, ships to production and can be found in the repository.
  • Proxy header trust: SECURE_PROXY_SSL_HEADER set while the proxy passes client-supplied X-Forwarded-Proto through, which Django warns can create CSRF weaknesses.
  • Request-driven queries: filter(**request.GET.dict()) and user-controlled order_by, the pattern behind CVE-2025-64459.
  • Media on the main origin: user uploads served from the application domain, turning a polyglot upload into stored XSS.

Practitioner checklist

  • Run manage.py check --deploy with production settings in CI and resolve every warning or document why it does not apply.
  • Set DEBUG = False, explicit ALLOWED_HOSTS, secure cookies, SSL redirect and staged HSTS.
  • Load SECRET_KEY and database credentials at runtime, never commit them, and rotate using SECRET_KEY_FALLBACKS.
  • Ban string-formatted SQL, extra() with user input and **request dictionary expansion into querysets.
  • List fields explicitly on every ModelForm and serializer.
  • Scope object lookups to the requesting user or tenant in get_queryset().
  • Add login and password reset throttling, and protect the admin with network restrictions and MFA.
  • Serve user media from a separate domain and cap request sizes at the web server.
  • Track Django security releases and patch within a defined window on a supported series.

Standards and references

Related NHI Mgmt Group resources: Python Security Guide · Flask Security Guide · Ruby on Rails Security Guide · Secure Code Review Guide

Free weekly newsletter

Subscribe to the NHI & AI Identity Journal

The latest on NHI and Agentic AI security – articles, research, breaches, news and events every week.

Bonus 33% off our NHI Course when you subscribe.

NHIMG Editorial Note
Researched and written by the NHI Mgmt Group editorial team using AI tools, under the editorial direction of Lalit Choda. Facts are drawn from the sources listed on this page and checked by our automated quality process. Last updated 26 October 2026. Spotted an error? Use our contact page.
NHI Mgmt Group — the #1 independent authority on Non-Human Identity, IAM, and Agentic AI security. nhimg.org