# PYTHON-FLASK-AUDIT-003: Flask Bound to All Interfaces

> **Severity:** MEDIUM | **CWE:** CWE-668 | **OWASP:** A05:2021

- **Language:** Python
- **Category:** Flask
- **URL:** https://codepathfinder.dev/registry/python/flask/PYTHON-FLASK-AUDIT-003
- **Detection:** `pathfinder scan --ruleset python/PYTHON-FLASK-AUDIT-003 --project .`

## Description

This rule detects Flask applications configured to bind the development server to 0.0.0.0,
which means the server listens on every available network interface -- loopback, LAN, Wi-Fi,
and any VPN or container bridge. The Flask built-in server (Werkzeug) is not a production
server: it is single-threaded, lacks connection limiting, and was designed for local
development only. Exposing it on all interfaces makes it reachable from any machine on the
same network, any other container in the same Docker network, or (if no firewall is in place)
the public internet.

When combined with debug=True (see PYTHON-FLASK-001), binding to 0.0.0.0 exposes the
Werkzeug interactive debugger to the network -- a remote code execution vulnerability
accessible to anyone who can reach the port.

The detection uses FlaskApp.method("run").where("host", "0.0.0.0") -- a QueryType-based
precise match. FlaskApp declares fqns=["flask"], so only objects imported from the flask
package are matched. The .where("host", "0.0.0.0") filter means only calls where the host
keyword argument is exactly the string "0.0.0.0" are flagged. Calls with host="127.0.0.1",
host="localhost", or a variable are not flagged. This precision delivers zero false positives
on correctly configured applications.


## Vulnerable Code

```python
from flask import Flask

app = Flask(__name__)
app.run(host="0.0.0.0")
```

## Secure Code

```python
import os
from flask import Flask

app = Flask(__name__)

@app.route('/api/status')
def status():
    return {'status': 'ok'}

if __name__ == '__main__':
    # SAFE: Bind to localhost only for local development
    app.run(host='127.0.0.1', port=5000)

    # For production: use a WSGI server behind a reverse proxy.
    # Gunicorn binds to 127.0.0.1 by default; the reverse proxy
    # (nginx, caddy) handles external traffic.
    # gunicorn -w 4 -b 127.0.0.1:8000 app:app

```

## Detection Rule (Python SDK)

```python
from rules.python_decorators import python_rule
from codepathfinder import calls, Or, QueryType

class FlaskApp(QueryType):
    fqns = ["flask"]


@python_rule(
    id="PYTHON-FLASK-AUDIT-003",
    name="Flask Bound to All Interfaces",
    severity="MEDIUM",
    category="flask",
    cwe="CWE-200",
    tags="python,flask,network,binding,CWE-200",
    message="Flask app bound to 0.0.0.0 (all interfaces). Bind to 127.0.0.1 in production.",
    owasp="A05:2021",
)
def detect_flask_bind_all():
    """Detects app.run(host='0.0.0.0')."""
    return FlaskApp.method("run").where("host", "0.0.0.0")
```

## How to Fix

- Bind to 127.0.0.1 (or localhost) for local development so only processes on the same machine can reach the server.
- Never run the Flask built-in development server in production. Use Gunicorn, uWSGI, or Waitress behind a reverse proxy.
- If you need the server reachable from other machines during development (e.g., testing on a mobile device), use a firewall rule or VPN tunnel rather than binding to 0.0.0.0.
- In Docker, use explicit port mapping (-p 127.0.0.1:5000:5000) rather than -p 5000:5000 to avoid binding to all interfaces on the host.
- Audit all app.run() calls in CI with this rule to catch 0.0.0.0 bindings before they reach shared or cloud environments.

## Security Implications

- **Development Server Exposed on Local Network:** Binding to 0.0.0.0 makes the Flask development server reachable from any machine on the
same network segment. In office or co-working environments this means colleagues can access
the app. In containerized environments it means other containers in the same Docker network
have direct access to the development server and any admin routes it exposes.

- **Werkzeug Debugger Reachable Over Network:** If debug mode is also enabled (app.run(debug=True, host="0.0.0.0")), the Werkzeug
interactive debugger becomes reachable from any machine that can route to the port. Any
triggered exception opens a Python console to the remote caller -- unauthenticated remote
code execution over the network.

- **Unintended Public Exposure Without Firewall:** Cloud instances, VMs, and development machines without strict egress/ingress firewall
rules will expose the Flask server to the internet when host is set to 0.0.0.0. Developer
laptops connected to public Wi-Fi are particularly vulnerable.

- **Sensitive Routes Accessible Without Authentication:** Development builds frequently include admin panels, debug endpoints (/debug, /admin), and
health-check routes that return internal state. Binding to all interfaces makes these
routes accessible to unintended callers without any access control.


## FAQ

**Q: Why is binding to 0.0.0.0 risky if I'm just doing local development?**

"Local development" often means a laptop on a shared office network, a VM in a cloud
account, or a container in a shared Docker environment. Any of these can expose 0.0.0.0
to other machines. If debug mode is also on, that means remote code execution is available
to anyone on the same network. The risk is not theoretical -- it has been exploited in
real incidents against developer machines on public Wi-Fi.


**Q: I need to test my app from a phone on the same Wi-Fi. What should I do instead?**

Use ngrok or a similar tunnel tool to expose a specific port securely, or configure your
machine's firewall to allow connections from your phone's IP only. Both approaches give
you external access without permanently binding to 0.0.0.0 in your source code.


**Q: Does this rule flag 0.0.0.0 in Docker-compose files or environment variables?**

No. This rule matches Python source code only, specifically the app.run(host="0.0.0.0")
call. Docker-compose port mappings and environment-variable-driven host settings are
separate concerns not covered by this rule.


**Q: How does the .where() filter make this rule precise?**

Without .where(), the rule would flag every app.run() call regardless of the host
argument -- that would include safe calls with host="127.0.0.1". The .where("host",
"0.0.0.0") constraint means only the literal string "0.0.0.0" triggers a finding.
If you pass the host from a variable (host=bind_address), the rule does not flag it,
since the value is not statically known to be "0.0.0.0".


**Q: Should I just remove the host argument entirely?**

Yes. Flask's app.run() defaults to host="127.0.0.1" when no host is specified. Omitting
the host argument is the safest option for local development, as it binds to localhost
only and will not flag this rule.


**Q: How do I run this rule in CI/CD?**

Run: pathfinder ci --ruleset python/flask/PYTHON-FLASK-AUDIT-003 --project .
The rule outputs SARIF, JSON, or CSV and can post inline pull request comments on GitHub.


**Q: Does this also catch binding via app.config or FLASK_RUN_HOST?**

No. This rule matches the app.run(host="0.0.0.0") call-site pattern only. Configuration
via app.config["HOST"] or the FLASK_RUN_HOST environment variable used with flask run is
not covered. These are separate patterns that would require a dedicated audit rule.


## References

- [CWE-668: Exposure of Resource to Wrong Sphere](https://cwe.mitre.org/data/definitions/668.html)
- [Flask Development Server Documentation](https://flask.palletsprojects.com/en/stable/server/)
- [Flask Deploying to Production](https://flask.palletsprojects.com/en/stable/deploying/)
- [OWASP A05:2021 Security Misconfiguration](https://owasp.org/Top10/A05_2021-Security_Misconfiguration/)
- [Docker Port Binding Security](https://docs.docker.com/network/)
- [Werkzeug Development Server Warning](https://werkzeug.palletsprojects.com/en/stable/serving/)

---

Source: https://codepathfinder.dev/registry/python/flask/PYTHON-FLASK-AUDIT-003
Code Pathfinder — Open source, type-aware SAST with cross-file dataflow analysis
