Join our Newsletter — 33% off our NHI Course
Home› Guides› Exposed Debug Modes Guide: Find and Shut Werkzeug,…
Guide Web & API Security

Exposed Debug Modes Guide: Find and Shut Werkzeug, Django and Laravel Debug Leaks

PDF for newsletter subscribers · free to read online

← All guides
By NHI Mgmt Group, edited by Lalit Choda Updated 13 November 2026 13 min read
On this page

A framework debug mode left on in production turns every unhandled error into a disclosure of secrets, source code and, in the worst cases, a remote code execution console. Werkzeug's interactive debugger (used by Flask), Django's DEBUG setting and Laravel's APP_DEBUG with the Ignition error page are convenient in development and dangerous anywhere reachable from a network. The framework maintainers are explicit: Werkzeug's documentation says the debugger must never run on production machines, Django's says never to deploy with DEBUG on, and Laravel's says APP_DEBUG should always be false in production. This guide explains what each mode exposes, how to find exposed instances across an estate, how to prevent them in build and deployment pipelines, and what to do when one is found. It is for application security engineers, platform teams and developers. Related exposure through stray files is covered in the Directory Listing and Exposed Backup Files Guide.

Key takeaways

  • Werkzeug's debugger is code execution by design. Its PIN and trusted host checks are, in the maintainers' words, not meant to fully secure it; the only safe production state is off.
  • Django and Laravel debug pages leak configuration. Tracebacks show settings, local variables, paths and library versions, and filtering of names containing KEY or SECRET does not catch every secret.
  • Ignition turned a debug page into a KEV entry. CVE-2021-3129 in Laravel Ignition before 2.5.2 allowed unauthenticated code execution and is in the CISA Known Exploited Vulnerabilities catalogue with known ransomware use.
  • Treat an exposure as a credential incident. If a debug page was reachable, assume every secret it could display has been read, and rotate them.
  • Prevent in the pipeline, verify from outside. Fail builds that ship debug flags, run framework deployment checks, and scan public endpoints for debug signatures continuously.

What each debug mode exposes

FrameworkHow it is switched onWhat an outsider can see or doBuilt-in guard and its limit
Werkzeug / Flaskflask run --debug, app.run(debug=True), or the Werkzeug DebuggedApplication middleware with evalex enabledInteractive tracebacks with a Python console in every stack frame, able to execute arbitrary code as the application userPIN prompt (on by default, printed to the server console, disabled if WERKZEUG_DEBUG_PIN is set to off) and a trusted host list; the docs say these are not meant to secure the debugger entirely
DjangoDEBUG = True in settings (the startproject template sets it, the framework default is False)Full tracebacks with source excerpts, local variables, request data, all settings and installed libraries; every SQL query is kept in memorySettings whose names contain API, KEY, PASS, SECRET, SIGNATURE or TOKEN are masked; secrets stored under other names, or held in local variables, are not
LaravelAPP_DEBUG=true in .env, read by the debug option in config/app.php (fallback false)Detailed error page (Ignition in many versions) with stack trace, request, environment and configuration valuesNone that protects production; the Laravel docs warn that true in production risks exposing sensitive configuration values to end users

Werkzeug and Flask in more detail

The Werkzeug debugger evaluates expressions in any traceback frame with that frame's local state preserved, so anyone who reaches it can read memory, environment variables and files and run code with the application's privileges. In current Werkzeug releases the console is served only to trusted hosts (localhost, its subdomains and 127.0.0.1 by default) and returns 400 to others, and a PIN is required the first time a console opens. Too many wrong PIN attempts require a server restart. Those guards help on a developer laptop. They do not help when a reverse proxy forwards traffic with a trusted Host header, when the PIN has been turned off for convenience, or when someone who can read the server logs or process environment can recover the PIN. Werkzeug's own definition of production is broad: anything that is not development and anything publicly accessible. Flask's documentation adds that the development server itself must not be used in production; use a production WSGI server and an error tracking or logging tool instead.

Django in more detail

With DEBUG on, Django renders its technical 500 page to any visitor who triggers an exception, and its technical 404 page lists URL patterns, which maps the application for an attacker. The settings filter is name-based: a password under a key containing PASS is masked, but a connection string stored under a neutral name such as DATABASE_URL, or a secret held in a local variable in the failing view, appears in clear text. The deployment checklist also notes the memory cost, because every SQL query is retained. When DEBUG is False, ALLOWED_HOSTS must be set or every request fails with 400, which is one reason teams flip DEBUG back on during an outage and forget to flip it off. The Django Security Guide covers the wider settings baseline.

Laravel and Ignition in more detail

Laravel's error page in debug mode shows the exception, stack frames with source, request details and application context. In affected versions, the Ignition package also exposed endpoints used for its "solutions" feature. CVE-2021-3129, an insecure use of file_get_contents() and file_put_contents() in Ignition, let unauthenticated attackers execute code on sites running in debug mode; Ignition 2.5.2 fixed it, and CISA added it to the KEV catalogue on 18 September 2023 with known ransomware campaign use. The Laravel APP_KEY, which protects encrypted cookies and session data, is a configuration value of exactly the kind an exposed debug page can disclose. The Laravel Security Guide covers the rest of the framework.

Why exposures happen

  • Template defaults carried forward: Django's startproject template ships with DEBUG = True and many tutorials end with app.run(debug=True).
  • Environment drift: a staging .env file with APP_DEBUG=true is copied to production, or a container image built for staging is promoted unchanged.
  • Outage debugging: an engineer enables debug to diagnose a production error and the change is never reverted.
  • "Internal" services that are not: admin tools, preview environments and pull request deployments get public DNS names or cloud load balancers.
  • Development servers in containers: a Dockerfile runs flask run --debug or php artisan serve as its entry point.

These are the same root causes behind most entries in the Misconfiguration exposures collection: a development convenience reaches production because nothing checks for it.

Finding exposed debug modes across an estate

Work from configuration inwards and from the internet outwards, because each finds what the other misses.

MethodWhat to look forCatchesMisses
Repository and IaC scanningDEBUG = True in production settings modules, APP_DEBUG=true in committed env files or Helm values, debug=True in app.run calls, --debug in Dockerfile or Procfile entry points, WERKZEUG_DEBUG_PIN=offMistakes before deploymentValues injected at runtime or changed by hand on servers
Runtime configuration inventoryEnvironment variables of running containers and app services (APP_DEBUG, DJANGO_DEBUG or equivalent, FLASK_DEBUG)Drift after deploymentHosts outside the inventory
Framework self-checkspython manage.py check --deploy against production settingsDEBUG and related insecure Django settingsOther frameworks
External attack surface scanningResponse signatures of debug pages on error: Werkzeug debugger markup, Django technical 500 and 404 pages, Laravel or Ignition error pages; requests to known debug console and Ignition paths returning anything other than 404Shadow and forgotten appsApps that never error during the scan
Log and WAF reviewInbound requests to debug console or Ignition paths, error responses with large bodies, 500 responses containing traceback markersActive probing by othersNothing if logging is off

Run scanning only against systems you own or are authorised to test. Scanners do not need to exploit anything: the presence of a debug page in an error response is the finding. Attackers do the same discovery at internet scale with wordlist scanning (ATT&CK T1595.003) and host information gathering (T1592), so a debug page on a public address should be expected to be found.

Preventing debug modes in production

  1. Make production the default: set the framework default to off and require an explicit development flag to turn debug on. In Django, read DEBUG from the environment and default to False; in Laravel, rely on the false fallback in config/app.php and never set APP_DEBUG in production environment configuration; in Flask, never pass debug=True in code that ships.
  2. Use production servers: run Flask under a production WSGI server, Django under a WSGI or ASGI server, and Laravel behind PHP-FPM, never the development servers.
  3. Exclude debug tooling from production builds: install development dependencies (such as debug toolbars and error page packages where they are dev-only in your version) only in development, for example with composer install --no-dev for PHP.
  4. Gate in CI: add a pipeline check that fails when production configuration contains a debug flag, and run manage.py check --deploy for Django projects. Test it by committing a deliberate violation on a branch and confirming the build fails.
  5. Gate at deploy time: admission policies for Kubernetes or deployment checks for app platforms that reject workloads with debug environment variables set to true in production namespaces.
  6. Return generic errors: custom 404 and 500 pages and server-side error reporting (Django ADMINS and LOGGING, Laravel logging channels, an error tracking service) give developers the detail without showing it to users.
  7. Verify from outside: after each release, trigger a harmless error path in a smoke test and assert that the response is the generic page.

Roll out the CI gate in warning mode first, fix the findings, then switch it to blocking. If a blocking gate stops an urgent release, the rollback is to the previous build, not to bypassing the check.

Choosing a debugging approach for production problems

NeedSafe approachAvoid
See the stack trace of a production errorError tracking service or structured logs with request IDsTurning on DEBUG or APP_DEBUG
Inspect state interactivelyReproduce in staging with production-like data that is masked; or attach an external debugger on the host, restricted to your address and enabled briefly, as Flask's docs describeWerkzeug console reachable over the network
Profile performanceAPM tooling with access controlPublic debug toolbars or profilers
Diagnose a single user's issueFeature-flagged verbose logging for that request ID, written to logs onlyPer-user debug pages rendered in the browser

Responding to an exposed debug mode

  1. Contain: turn debug off and redeploy, or block the host at the edge if a redeploy will take time. Confirm externally that the generic error page now appears.
  2. Preserve evidence: keep web server, load balancer and WAF logs for the exposure window, the deployed configuration and the image digest.
  3. Scope: determine when debug was enabled (deployment history, configuration change logs) and search logs for requests to debug console or Ignition paths and for error responses served to external addresses.
  4. Rotate: treat every secret visible to the application as exposed: framework keys (Django SECRET_KEY, Laravel APP_KEY), database passwords, cloud access keys, API tokens for payment, email and messaging providers. For Werkzeug or a vulnerable Ignition version, also treat the host as potentially compromised and rebuild it.
  5. Hunt: look for new users, web shells, scheduled tasks and outbound connections from the host, and for use of the rotated credentials from unfamiliar addresses before rotation.
  6. Fix the cause: add the CI and deploy-time gates that would have caught this instance.

Where identity fits

Most of the damage from an exposed debug page comes from non-human credentials: the application's database user, its cloud access keys, the SMTP or payment API tokens and the framework signing keys. ATT&CK tracks harvesting of such secrets from configuration as T1552.001 Credentials In Files. Keeping secrets out of environment files and process environment, fetching them at runtime from a secrets manager with short-lived credentials, and scoping each credential tightly all reduce what a debug page can reveal and make rotation fast. See Environment Variables and .env Files.

Measuring the control

  • External exposure: number of internet-facing hosts returning a framework debug page (target zero), from continuous scanning.
  • Gate coverage: percentage of Python and PHP web repositories with a CI check for debug flags.
  • Time to close: hours from detection of an exposed debug page to confirmed fix.
  • Rotation completeness: percentage of secrets rotated after each exposure, against the inventory of secrets the application held.
  • Drift findings: runtime configuration findings where debug is on in a production environment, per month.

Framework mapping

AreaReferenceRequirement or control
Debug disabled in productionOWASP ASVS 5.0V13.4.2 debug modes disabled for all components in production
Generic error messagesOWASP ASVS 5.0V16.5.1 generic message for unexpected or security-sensitive errors, with no stack traces, queries, keys or tokens
Monitoring and documentation endpointsOWASP ASVS 5.0V13.4.5 documentation and monitoring endpoints not exposed unless intended
Weakness classMITRE CWECWE-489 Active Debug Code
Secure configuration baselineNIST SP 800-53 Rev 5CM-2 Baseline Configuration, CM-6 Configuration Settings, CM-7 Least Functionality
Error handlingNIST SP 800-53 Rev 5SI-11 Error Handling
Configuration managementNIST CSF 2.0PR.PS-01 configuration management practices are established and applied
Exploitation and discoveryMITRE ATT&CKT1190 Exploit Public-Facing Application, T1595.003 Wordlist Scanning, T1552.001 Credentials In Files

Common failure patterns

  • Trusting the PIN: leaving the Werkzeug debugger on behind a proxy because a PIN is required, when the maintainers state it is not a security boundary.
  • Trusting the settings filter: assuming Django masks all secrets, while a credential stored under a neutral setting name or in a local variable appears in the traceback.
  • The temporary fix that stays: APP_DEBUG=true set during an incident call and still on months later.
  • Preview environments forgotten: branch deployments with debug on and real third-party API keys, reachable on public URLs.
  • Turning debug off without rotating: closing the exposure but leaving the database password and APP_KEY that were displayed unchanged.
  • Patching Ignition but leaving debug on: fixing CVE-2021-3129 while the error page still discloses configuration to anyone.

Practitioner checklist

  • Search all repositories and deployment configuration for DEBUG = True, APP_DEBUG=true, debug=True, --debug entry points and WERKZEUG_DEBUG_PIN.
  • Inventory runtime environment variables of production workloads for debug flags.
  • Run manage.py check --deploy for every Django project against production settings.
  • Confirm production Flask, Django and Laravel apps run under production servers, not development servers.
  • Confirm Ignition is at 2.5.2 or later wherever it is installed, and that debug is off regardless.
  • Add a blocking CI check and a deploy-time policy for debug flags in production.
  • Scan external endpoints continuously for framework debug page signatures.
  • Serve custom error pages and route error detail to logging or error tracking.
  • Keep a rotation runbook for every secret each application holds, and run it after any debug exposure.

Standards and references

Related NHI Mgmt Group resources: Directory Listing and Exposed Backup Files Guide · Flask Security Guide · Django Security Guide · Laravel Security Guide · Environment Variables and .env Files

PDF for subscribers

Take this guide with you

Every section, table and checklist in one PDF, with links to the sources and related guides. Subscribe to our weekly NHI & AI Identity Journal and we’ll email you a download link.

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 13 November 2026. Spotted an error? Use our contact page.
NHI Mgmt Group, the independent authority on Non-Human Identity, IAM and Agentic AI security. nhimg.org

PDF Downloads for Newsletter Subscribers

Get the guide

Enter your details and we’ll email you a download link.

The full guide is free to read online. The PDF is a thank you for subscribing.

Fill in all fields marked * and tick the PDF box to continue.