VNX-PY-006: Django DEBUG=True
Detect Django deployment settings modules that set DEBUG = True or default DEBUG to on when the environment variable is unset, exposing tracebacks, settings and SQL to any visitor.
| Field | Value |
|---|---|
| Rule ID | VNX-PY-006 |
| Severity | Medium |
| SARIF level | warning |
| Kind | sast |
| Languages | Python |
| CWE | CWE-489 |
| CAPEC | CAPEC-116 |
| ATT&CK | T1190 |
| CWSS vector | CWSS:1.0/TI:M/AP:RU/AL:S/IC:N/FC:LT/RP:RU/RL:S/AV:L/AS:N/IN:A/SC:A/BI:M/DI:H/EX:M/EC:N/P:C |
| Tags | django config debug |
| Source | internal/sast/rules/vnx-py-006.rego |
Overview
This rule flags DEBUG = True (or an environment read that defaults to on) in Django deployment settings modules (settings.py, settings/production.py and other non-development modules of a settings/ package). 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.
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
A top-level DEBUG = True, or DEBUG read from the environment with a default of on, in a Django settings module: settings.py (which must look like Django), settings_<name>.py/<name>_settings.py, or any module of a settings/ package.
Not flagged: development, local, test and CI modules (settings/dev.py, local.py, settings_test.py), a base.py whose sibling production module sets DEBUG = False, DEBUG = True inside an if block, environment reads that default to False, DEBUG_TOOLBAR/TEMPLATE_DEBUG, test paths, and a settings.py that is not Django’s.
# FLAGGED: settings.py
DEBUG = True
# FLAGGED: production settings module still has debug on
# settings/production.py
from .base import *
DEBUG = True
# FLAGGED: debug is on whenever the variable is unset
DEBUG = os.environ.get("DJANGO_DEBUG", True)
DEBUG = os.getenv("DEBUG", "True") == "True"
DEBUG = env.bool("DEBUG", default=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"]
References
- CWE-489: Active Debug Code
- Django documentation – DEBUG setting
- Django documentation – Deployment checklist
- OWASP Top 10 A05:2021 – Security Misconfiguration
- OWASP Application Security Verification Standard – V14 Configuration
- CAPEC-116: Excavation
- MITRE ATT&CK T1190 – Exploit Public-Facing Application
Suppressing this rule
Put a nosec comment on the line, or on line 1 to cover the whole file. Use the comment marker of the file’s language (#, //, -- or ;):
# nosec vnx-py-006
// nosec vnx-py-006
Or record an ignore rule: vulnetix ignore add --rule vnx-py-006 --reason "...". See Suppressing findings.