Flask Debug Mode Enabled

HIGH

Detects Flask applications started with debug=True, which enables the interactive Werkzeug debugger and must never run in production.

Rule Information

Language
Python
Category
Flask
Author
Shivasurya
Shivasurya
Last Updated
2026-03-22
Tags
pythonflaskdebug-modeconfigurationinformation-disclosurewerkzeugproductionmisconfigurationremote-code-executionCWE-489OWASP-A05
CWE References

Interactive Playground

Experiment with the vulnerable code and security rule below. Edit the code to see how the rule detects different vulnerability patterns.

pathfinder scan --ruleset python/PYTHON-FLASK-001 --project .
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
rule.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

About This Rule

Understanding the vulnerability and how it is detected

This rule detects Flask applications that call app.run(debug=True), enabling the interactive Werkzeug debugger. When debug mode is active, any unhandled exception opens an interactive Python console directly in the browser. An attacker who can trigger a 500 error -- by sending malformed input, hitting an edge case, or guessing a route -- gains the ability to execute arbitrary Python code in the server process. This is unauthenticated remote code execution, not a theoretical risk.

Beyond the debugger itself, debug mode also exposes full stack traces with local variable values in HTTP responses, enables the auto-reloader, and disables several Flask security hardening behaviors. Stack traces leak framework versions, file system paths, configuration values, and application logic that attackers use to plan further exploitation.

The detection uses FlaskApp.method("run").where("debug", True) -- a QueryType-based precise match. FlaskApp declares fqns=["flask"], so the engine only matches objects actually imported from the flask package. The .where("debug", True) filter means only calls where the debug keyword argument is explicitly the boolean True are flagged. Calls with debug=False, debug=os.getenv(...), or no debug argument at all are not flagged. This precision delivers zero false positives on properly configured applications.

Security Implications

Potential attack scenarios if this vulnerability is exploited

1

Unauthenticated Remote Code Execution via Werkzeug Debugger

The Werkzeug interactive debugger opens a Python console on every unhandled exception. Any visitor who can trigger a 500 error can execute arbitrary Python in the server process. There is a PIN mechanism in Werkzeug 0.11+, but it is derived from machine details (hostname, MAC address, process ID, app path) that are frequently leaked through stack traces or accessible via /proc on Linux. Do not treat the PIN as a security control.

2

Full Stack Traces Leaked in HTTP Responses

Debug mode returns detailed exception tracebacks to the browser, including local variable values, function call chains, and source file paths. Attackers use this to map the application's internal structure, identify library versions with known CVEs, and locate exact lines of code to target with follow-up attacks.

3

Secret and Configuration Disclosure

Stack traces frequently capture the values of configuration variables, database connection strings, API keys, and Flask's SECRET_KEY at the moment of the exception. A single triggered error can expose credentials that give an attacker direct database access or the ability to forge session cookies.

4

Persistent Exposure Through Reloader

The debug reloader watches the file system for source changes and restarts the app automatically. In containerized environments with mounted volumes, this can expose file system paths, trigger import errors that produce verbose stack traces, and create timing side-channels that reveal the application's directory structure.

How to Fix

Recommended remediation steps

  • 1Remove debug=True from all app.run() calls. The default is False -- omitting the argument is safe and explicit.
  • 2Drive debug mode through an environment variable (FLASK_DEBUG) and verify it is set to 0 or false in every production, staging, and CI environment.
  • 3Replace app.run() with a production WSGI server such as Gunicorn, uWSGI, or Waitress. These servers never expose the Werkzeug debugger regardless of configuration.
  • 4Implement a global exception handler (app.errorhandler(500)) that returns a generic error page to users and logs the full traceback server-side only.
  • 5Add a pre-deployment check in CI -- run this rule with pathfinder ci -- to catch accidental re-introduction of debug=True before it reaches production.

Detection Scope

How Code Pathfinder analyzes your code for this vulnerability

This rule uses a QueryType-based precise match: FlaskApp.method("run").where("debug", True). FlaskApp declares fqns=["flask"], restricting matches to objects imported from the flask package. The .method("run") step targets the run() method call on that object. The .where("debug", True) step filters to calls where the keyword argument debug is explicitly the boolean True. Calls with debug=False, debug=0, debug=variable_name, or no debug argument are not flagged. This precision eliminates false positives from correctly configured applications. The rule operates at the call-site level without cross-file dataflow analysis.

Compliance & Standards

Industry frameworks and regulations that require detection of this vulnerability

OWASP Top 10
A05:2021 - Security Misconfiguration: disable debug features in production
CWE Top 25
CWE-489 Active Debug Code -- shipping debug-enabled code to production
PCI DSS v4.0
Requirement 6.2.4 -- prevent information disclosure through error messages
NIST SP 800-53
SI-11: Error Handling -- error messages must not expose system internals

References

External resources and documentation

Similar Rules

Explore related security rules for Python

Frequently Asked Questions

Common questions about Flask Debug Mode Enabled

The Werkzeug debugger PIN is computed from the machine's hostname, the first network interface's MAC address, the process ID, and the app's root path. These values are frequently leaked through stack traces, accessible via /proc on Linux containers, or guessable on shared hosting. Researchers have demonstrated PIN bypasses against real deployments. The PIN is a speed bump, not a security boundary.
The QueryType FlaskApp declares fqns=["flask"], so the engine traces the import of the object back to the flask package. A custom class with a run(debug=True) method that is not imported from flask will not be flagged. This import-aware matching is what makes the .where() approach produce zero false positives.
Yes. The rule cannot distinguish development files from production code without path-based filtering. You can suppress findings in local scripts by adding a path exclusion in your Code Pathfinder configuration, or by switching local development to use Flask's built-in test client (app.test_client()) which never starts the HTTP server.
FLASK_DEBUG is an environment variable Flask reads at startup. This rule catches debug=True in the Python source code -- a hardcoded value. If you rely solely on environment variables, this rule will not flag your code, but you still need to ensure FLASK_DEBUG is not set to 1 in production. Use environment variable auditing as a complementary control.
Run: pathfinder ci --ruleset python/flask/PYTHON-FLASK-001 --project . The rule outputs SARIF, JSON, or CSV. On GitHub Actions it can post inline pull request comments pointing to the exact line where debug=True appears, blocking the merge before the misconfiguration reaches production.
No. This rule specifically matches the app.run(debug=True) call-site pattern. Configuration set via app.config["DEBUG"] = True is a separate pattern not covered here. For comprehensive debug-mode coverage, combine this rule with a configuration assignment audit rule.
Gunicorn (gunicorn app:app) is the most common choice. uWSGI, Waitress (pure Python, works on Windows), and Hypercorn (supports ASGI) are also solid options. All of them ignore Flask's debug setting -- the Werkzeug development server that app.run() starts is never invoked in these deployments.

New feature

Get these findings posted directly on your GitHub pull requests

The Flask Debug Mode Enabled rule runs in CI and posts inline review comments on the exact lines — no dashboard, no SARIF viewer.

See how it works