VNX-PY-006 – Django DEBUG=True in Production
Overview
This rule flags DEBUG = True in Django settings files (settings.py, settings/base.py, settings/production.py, settings/prod.py). When Django’s debug mode is enabled it renders a full HTML error page for every unhandled exception, including the complete Python traceback, local variable values at every stack frame, the full list of Django settings, and the SQL queries that ran during the request. This information gives an attacker a detailed map of your application’s internals without needing any special access. This maps to CWE-489: Active Debug Code.
Severity: Medium | CWE: CWE-489 – Active Debug Code
Why This Matters
Django’s debug error page is designed for development — it is intentionally information-dense to help developers understand failures quickly. In production that same information becomes a reconnaissance gift for attackers. A single unhandled exception (triggered by a malformed request, a 404 on a guessable path, or an invalid query parameter) reveals:
- Database schema — table names, column names, and query parameters visible in the SQL panel
- File system paths — absolute paths to all Python source files in the traceback
- Configuration values — the full
settingsmodule printed in the error page, potentially includingSECRET_KEY, database credentials, and API keys if they were not loaded from the environment - Source code context — 5 lines of source around each frame in the traceback
- Installed apps and middleware — the full application architecture
Additionally, DEBUG = True causes Django to keep an in-memory list of every SQL query executed since the process started, which grows unboundedly and can cause memory exhaustion under load.
What Gets Flagged
Any line matching DEBUG = True (with optional surrounding whitespace) in a Django settings file.
# FLAGGED: settings.py
DEBUG = True
# FLAGGED: with leading whitespace
DEBUG = True
# FLAGGED: production settings file still has debug on
# settings/production.py
DEBUG = True
Remediation
- Set DEBUG to False unconditionally in production settings. The simplest approach is to have a separate settings file for production that explicitly sets
DEBUG = False:
# settings/production.py
DEBUG = False
- Read DEBUG from an environment variable so it can be controlled at deployment time without code changes. This is the recommended pattern for twelve-factor applications:
# settings.py
import os
DEBUG = os.environ.get("DJANGO_DEBUG", "False").lower() == "true"
With this pattern, DEBUG defaults to False unless DJANGO_DEBUG=true is explicitly set in the environment. Your local development environment sets the variable; your production environment does not.
- Ensure
ALLOWED_HOSTSis configured. WhenDEBUG = False, Django requiresALLOWED_HOSTSto be set to the exact hostnames your application serves. This prevents HTTP Host header injection:
# settings/production.py
DEBUG = False
ALLOWED_HOSTS = ["yourdomain.example", "www.yourdomain.example"]
- Configure error reporting for production. With
DEBUG = False, Django will send exception details to the addresses inADMINSvia email (if mail is configured) or to Sentry / another error tracking service. Set this up so you do not lose visibility when errors occur:
ADMINS = [("Ops Team", "ops@yourcompany.example")]
# Or use django-sentry-sdk
import sentry_sdk
sentry_sdk.init(dsn=os.environ["SENTRY_DSN"])
- Check that
SECRET_KEYis not hard-coded. The debug page renderssettings, so any hard-coded secrets insettings.pyare exposed. LoadSECRET_KEYfrom the environment:
SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]