From be3398c5d1ce0fa44fd00433683873f18ce51cfa Mon Sep 17 00:00:00 2001 From: JuanERombado Date: Tue, 12 May 2026 12:22:47 -0500 Subject: [PATCH 1/2] fix: disable public Flask debug by default --- bcos_directory.py | 3 +- bridge/bridge_api.py | 3 +- contributor_registry.py | 3 +- explorer/app.py | 4 ++- keeper_explorer.py | 3 +- security_test_payment_widget.py | 3 +- tests/test_public_flask_debug.py | 50 ++++++++++++++++++++++++++++++++ 7 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 tests/test_public_flask_debug.py diff --git a/bcos_directory.py b/bcos_directory.py index c16ba0e82..a875f3c96 100644 --- a/bcos_directory.py +++ b/bcos_directory.py @@ -482,4 +482,5 @@ def serve_dist(filename): if __name__ == '__main__': init_db() load_projects_from_json() - app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file + debug = os.environ.get("FLASK_DEBUG", "").lower() in {"1", "true", "yes", "on"} + app.run(debug=debug, host='0.0.0.0', port=5000) diff --git a/bridge/bridge_api.py b/bridge/bridge_api.py index b3725932e..2338bdbb5 100644 --- a/bridge/bridge_api.py +++ b/bridge/bridge_api.py @@ -621,4 +621,5 @@ def register_bridge_routes(app: Flask): app = Flask(__name__) register_bridge_routes(app) print("Bridge dev server on http://0.0.0.0:8096") - app.run(host="0.0.0.0", port=8096, debug=True) + debug = os.environ.get("FLASK_DEBUG", "").lower() in {"1", "true", "yes", "on"} + app.run(host="0.0.0.0", port=8096, debug=debug) diff --git a/contributor_registry.py b/contributor_registry.py index abba61cd4..b8be67e1e 100644 --- a/contributor_registry.py +++ b/contributor_registry.py @@ -188,4 +188,5 @@ def approve_contributor(username): if __name__ == '__main__': if not os.path.exists(DB_PATH): init_db() - app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file + debug = os.environ.get("FLASK_DEBUG", "").lower() in {"1", "true", "yes", "on"} + app.run(debug=debug, host='0.0.0.0', port=5000) diff --git a/explorer/app.py b/explorer/app.py index 872a1019c..dcc869873 100644 --- a/explorer/app.py +++ b/explorer/app.py @@ -1,4 +1,5 @@ from flask import Flask, render_template, jsonify +import os import requests import json from datetime import datetime @@ -134,4 +135,5 @@ def internal_error(error): return render_template('500.html'), 500 if __name__ == '__main__': - app.run(host='0.0.0.0', port=5000, debug=True) \ No newline at end of file + debug = os.environ.get("FLASK_DEBUG", "").lower() in {"1", "true", "yes", "on"} + app.run(host='0.0.0.0', port=5000, debug=debug) diff --git a/keeper_explorer.py b/keeper_explorer.py index 8a46870e4..654b6d18d 100644 --- a/keeper_explorer.py +++ b/keeper_explorer.py @@ -377,4 +377,5 @@ def faucet_drip(): if __name__ == '__main__': import hashlib # needed for mock hash print(f"[*] Starting Fossil-Punk Keeper Explorer on port {PORT}...") - app.run(host='0.0.0.0', port=PORT, debug=True) + debug = os.environ.get("FLASK_DEBUG", "").lower() in {"1", "true", "yes", "on"} + app.run(host='0.0.0.0', port=PORT, debug=debug) diff --git a/security_test_payment_widget.py b/security_test_payment_widget.py index 5bbc35b31..67750937c 100644 --- a/security_test_payment_widget.py +++ b/security_test_payment_widget.py @@ -272,4 +272,5 @@ def admin_login(): if __name__ == '__main__': if not os.path.exists(DB_PATH): init_db() - app.run(debug=True, host='0.0.0.0', port=5000) \ No newline at end of file + debug = os.environ.get("FLASK_DEBUG", "").lower() in {"1", "true", "yes", "on"} + app.run(debug=debug, host='0.0.0.0', port=5000) diff --git a/tests/test_public_flask_debug.py b/tests/test_public_flask_debug.py new file mode 100644 index 000000000..98eab5de0 --- /dev/null +++ b/tests/test_public_flask_debug.py @@ -0,0 +1,50 @@ +import ast +from pathlib import Path + + +PUBLIC_FLASK_ENTRYPOINTS = [ + "bcos_directory.py", + "bridge/bridge_api.py", + "contributor_registry.py", + "explorer/app.py", + "keeper_explorer.py", + "security_test_payment_widget.py", +] + + +def _literal_keyword(call: ast.Call, keyword_name: str): + for keyword in call.keywords: + if keyword.arg == keyword_name: + return keyword.value + return None + + +def _is_app_run(call: ast.Call) -> bool: + return ( + isinstance(call.func, ast.Attribute) + and call.func.attr == "run" + and isinstance(call.func.value, ast.Name) + and call.func.value.id == "app" + ) + + +def test_public_flask_entrypoints_do_not_hardcode_debug_true(): + repo_root = Path(__file__).resolve().parents[1] + + for relative_path in PUBLIC_FLASK_ENTRYPOINTS: + source_path = repo_root / relative_path + tree = ast.parse(source_path.read_text(encoding="utf-8"), filename=relative_path) + + for node in ast.walk(tree): + if not isinstance(node, ast.Call) or not _is_app_run(node): + continue + + host = _literal_keyword(node, "host") + debug = _literal_keyword(node, "debug") + + binds_public_interface = isinstance(host, ast.Constant) and host.value == "0.0.0.0" + hardcodes_debug_true = isinstance(debug, ast.Constant) and debug.value is True + + assert not ( + binds_public_interface and hardcodes_debug_true + ), f"{relative_path} binds 0.0.0.0 with debug=True" From c2532b85a5a864141500b8cadfd889771405cb37 Mon Sep 17 00:00:00 2001 From: JuanERombado Date: Tue, 12 May 2026 12:59:14 -0500 Subject: [PATCH 2/2] test: add SPDX header to Flask debug regression --- tests/test_public_flask_debug.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_public_flask_debug.py b/tests/test_public_flask_debug.py index 98eab5de0..5479487d0 100644 --- a/tests/test_public_flask_debug.py +++ b/tests/test_public_flask_debug.py @@ -1,3 +1,4 @@ +# SPDX-License-Identifier: MIT import ast from pathlib import Path