Skip to content

Flask

Facts last verified 2026-07-29 against Flask 3.1.3 · Werkzeug 3.1.8 · Jinja2 3.1.6 · Flask-WTF 1.3.0

Flask is a small framework with a large development surface, and that shape decides this whole cluster. Almost nothing here is a setting somebody chose wrongly. It is either a development affordance that reached production, or a control that was never going to be there because Flask does not ship controls.

Two habits follow from that.

Check the invocation before the configuration. More of this cluster is decided by how the process was started than by what is in config.py — which is why the production server is a security page rather than a performance one.

Do not read the absence of a control as the absence of a need. Flask has no CSRF, no rate limiting and no body-size cap out of the box. That is a scope decision by the framework, not a statement about your application.

Terminal window
python3 -c "import flask, werkzeug, jinja2; print(flask.__version__, werkzeug.__version__, jinja2.__version__)"
pip list 2>/dev/null | grep -iE '^(flask|werkzeug|jinja2|itsdangerous|flask-wtf) '

Flask 3.1.3 on Werkzeug 3.1.8 is current. Two version boundaries matter here. TRUSTED_HOSTS arrived in 3.1 — set it on anything older and Flask ignores it silently, because unknown config keys are not rejected. And FLASK_ENV was removed in 2.3, so a deployment still setting it is controlling nothing; FLASK_DEBUG is the variable that matters now.

Terminal window
curl -s -o /dev/null -w '%{http_code}\n' https://your-app.example.com/console

A 200 there — even one asking for a PIN — is an interactive Python console on a public port. It is the cluster’s threat page, it is the most severe thing here by a wide margin, and it arrives through an environment variable rather than through anyone’s decision.

The PIN that guards it is derived from your username, the module path, the machine’s MAC address and its machine ID. Werkzeug’s own source comment describes half those inputs as existing “to make the cookie unique on the computer, not as a security feature”.

SECRET_KEY signs the session cookie, and the cookie is signed rather than encrypted — the payload base64-decodes to plaintext without it. Anyone holding the key can forge any session.

Four pages in this cluster end at that value: a debugger console reads it in one expression, a template injection reaches it as {{ config.SECRET_KEY }}, the session cookie is only as trustworthy as it is, and CSRF tokens are signed with it too. Read SECRET_KEY before the pages that depend on it.

Severity is the exposure a control closes, not how often it gets recommended. One entry is rated low and says so — request size limits, where a reverse proxy has usually capped the body already and the application limit is defence in depth.

SECRET_KEYForged sessions for any user, from a key committed to the repositorySeverity: critical
Debug modeTracebacks, source and an interactive console served to clientsSeverity: high
Session cookiesSession cookie sent over plaintext, attached to cross-site requests, or read by scriptSeverity: high
Template autoescapingStored XSS from a template whose extension is not on the autoescape listSeverity: high
Server-side template injectionTemplate expressions evaluated from user input, reaching application configSeverity: high
CSRF protectionState-changing requests forged from another site using the visitor's session cookieSeverity: medium
Serving filesArbitrary file read through a path built from request dataSeverity: medium
Trusted hostsAbsolute URLs and redirects built from an attacker-supplied Host headerSeverity: medium
Proxy headersA forged X-Forwarded-For entry becoming request.remote_addr, or a scheme the client choseSeverity: medium
The production serverA development server on a production port, one variable away from the debuggerSeverity: medium
Request size limitsMemory and disk exhaustion from unbounded request bodiesSeverity: low
The Werkzeug debugger consoleArbitrary code execution as the application user, from a browserSeverity: critical
0 of 12 applied

Python itself, the WSGI server’s TLS, the process manager, and the proxy in front of you. That is the server’s half rather than the application’s — see application vs server for the dividing line, and for the three controls that genuinely span both.