# Vulnerable: Debug mode enabled in Flask
from flask import Flask, request, jsonify
import os
import traceback
# Dangerous: Debug mode explicitly enabled
app = Flask(__name__)
app.debug = True # CRITICAL: Debug mode in production
# Another dangerous pattern
app.config['DEBUG'] = True
# Environment variable approach (still dangerous if set)
app.config['DEBUG'] = os.environ.get('FLASK_DEBUG', True)
@app.route('/api/data')
def get_data():
try:
# Some application logic
data = process_user_data(request.args)
return jsonify(data)
except Exception as e:
# Dangerous: Detailed error in debug mode
return jsonify({'error': str(e)}), 500
@app.route('/config')
def show_config():
# Dangerous: Configuration exposed in debug mode
return jsonify(dict(app.config))
# Dangerous: Manual debug info exposure
@app.route('/debug/info')
def debug_info():
if app.debug:
import sys
return jsonify({
'python_version': sys.version,
'python_path': sys.path,
'environment': dict(os.environ),
'config': dict(app.config)
})
return jsonify({'error': 'Debug not available'})
# Dangerous: Stack trace exposure
@app.errorhandler(500)
def handle_error(error):
if app.debug:
return jsonify({
'error': str(error),
'traceback': traceback.format_exc()
}), 500
return jsonify({'error': 'Internal server error'}), 500
# Running with debug mode
if __name__ == '__main__':
# Dangerous: Debug mode in run command
app.run(debug=True, host='0.0.0.0', port=5000)
# Configuration class approach (vulnerable)
class Config:
DEBUG = True # Dangerous: Always debug
SECRET_KEY = 'dev-secret-key'
DATABASE_URL = 'sqlite:///dev.db'
class ProductionConfig(Config):
# Still inherits DEBUG = True - dangerous!
SECRET_KEY = os.environ.get('SECRET_KEY')
DATABASE_URL = os.environ.get('DATABASE_URL')
app.config.from_object(ProductionConfig)
# Conditional debug (still dangerous)
def create_app(config_name='development'):
app = Flask(__name__)
if config_name == 'development':
app.debug = True
elif config_name == 'production':
# Dangerous: Debug might still be enabled elsewhere
pass
return app
# Environment-based but unsafe default
DEBUG_MODE = os.environ.get('DEBUG', 'True').lower() == 'true'
app.config['DEBUG'] = DEBUG_MODE
# Secure: Proper Flask debug mode configuration
from flask import Flask, request, jsonify
import os
import logging
from logging.handlers import RotatingFileHandler
# Safe: Debug mode properly controlled
app = Flask(__name__)
# Safe: Environment-based configuration with secure defaults
class Config:
SECRET_KEY = os.environ.get('SECRET_KEY') or 'you-will-never-guess'
DEBUG = False # Safe: Debug disabled by default
TESTING = False
LOG_LEVEL = logging.INFO
class DevelopmentConfig(Config):
DEBUG = True # Only enabled in development
LOG_LEVEL = logging.DEBUG
class ProductionConfig(Config):
DEBUG = False # Explicitly disabled in production
LOG_LEVEL = logging.WARNING
# Additional production-specific settings
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
PERMANENT_SESSION_LIFETIME = 1800
class TestingConfig(Config):
TESTING = True
DEBUG = False # Debug disabled in testing too
WTF_CSRF_ENABLED = False
# Safe: Configuration mapping
config = {
'development': DevelopmentConfig,
'production': ProductionConfig,
'testing': TestingConfig,
'default': ProductionConfig # Safe default
}
def create_app(config_name=None):
app = Flask(__name__)
# Safe: Use environment variable with secure default
if config_name is None:
config_name = os.environ.get('FLASK_ENV', 'production')
# Load configuration
app.config.from_object(config[config_name])
# Safe: Setup logging instead of debug mode
setup_logging(app)
# Safe: Custom error handlers
setup_error_handlers(app)
return app
def setup_logging(app):
if not app.debug and not app.testing:
# Production logging setup
if not os.path.exists('logs'):
os.mkdir('logs')
file_handler = RotatingFileHandler(
'logs/app.log',
maxBytes=10240000,
backupCount=10
)
file_handler.setFormatter(logging.Formatter(
'%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d]'
))
file_handler.setLevel(app.config['LOG_LEVEL'])
app.logger.addHandler(file_handler)
app.logger.setLevel(app.config['LOG_LEVEL'])
app.logger.info('Application startup')
def setup_error_handlers(app):
@app.errorhandler(404)
def not_found_error(error):
return jsonify({'error': 'Resource not found'}), 404
@app.errorhandler(500)
def internal_error(error):
# Safe: Log detailed error but don't expose to user
app.logger.error(f'Server Error: {error}', exc_info=True)
if app.debug:
# Only in development: show detailed error
import traceback
return jsonify({
'error': 'Internal server error',
'details': str(error),
'traceback': traceback.format_exc()
}), 500
else:
# Production: generic error message
return jsonify({'error': 'Internal server error'}), 500
@app.errorhandler(Exception)
def handle_exception(error):
# Log the error
app.logger.error(f'Unhandled exception: {error}', exc_info=True)
if app.debug:
# Re-raise in development for debugger
raise error
else:
# Return generic error in production
return jsonify({'error': 'An unexpected error occurred'}), 500
# Safe: Application routes with proper error handling
@app.route('/api/data')
def get_data():
try:
# Application logic
data = process_user_data(request.args)
return jsonify(data)
except ValueError as e:
# Safe: Log error, return user-friendly message
app.logger.warning(f'Invalid data request: {e}')
return jsonify({'error': 'Invalid request parameters'}), 400
except Exception as e:
# Safe: Log detailed error, return generic message
app.logger.error(f'Data processing error: {e}', exc_info=True)
return jsonify({'error': 'Unable to process request'}), 500
# Safe: Health check endpoint
@app.route('/health')
def health_check():
return jsonify({
'status': 'healthy',
'version': os.environ.get('APP_VERSION', '1.0.0'),
'environment': os.environ.get('FLASK_ENV', 'production')
# Note: No sensitive configuration exposed
})
# Safe: Debug info only in development
@app.route('/debug/info')
def debug_info():
if not app.debug:
return jsonify({'error': 'Debug information not available'}), 403
# Safe: Limited debug info even in development
import sys
return jsonify({
'debug_mode': app.debug,
'python_version': sys.version.split()[0],
'flask_env': os.environ.get('FLASK_ENV'),
'app_config': {
'DEBUG': app.config['DEBUG'],
'TESTING': app.config['TESTING']
# Note: Don't expose sensitive config like SECRET_KEY
}
})
# Safe: Configuration validation
def validate_config(app):
"""Validate critical configuration settings"""
# Check if debug is disabled in production
if os.environ.get('FLASK_ENV') == 'production' and app.debug:
raise RuntimeError('Debug mode cannot be enabled in production')
# Check for secure secret key in production
if (os.environ.get('FLASK_ENV') == 'production' and
app.config['SECRET_KEY'] in ['dev', 'development', 'you-will-never-guess']):
raise RuntimeError('Insecure SECRET_KEY in production')
# Validate other security settings
if os.environ.get('FLASK_ENV') == 'production':
required_secure_settings = {
'SESSION_COOKIE_SECURE': True,
'SESSION_COOKIE_HTTPONLY': True
}
for setting, expected_value in required_secure_settings.items():
if app.config.get(setting) != expected_value:
app.logger.warning(f'Insecure setting: {setting} should be {expected_value}')
# Safe: Application factory with validation
def create_validated_app(config_name=None):
app = create_app(config_name)
# Validate configuration before starting
validate_config(app)
return app
# Safe: Application startup
if __name__ == '__main__':
# Safe: Environment-controlled execution
flask_env = os.environ.get('FLASK_ENV', 'production')
app = create_validated_app(flask_env)
if flask_env == 'development':
# Development server with debug
app.run(debug=True, host='127.0.0.1', port=5000)
else:
# Production: use proper WSGI server
app.logger.warning('Use a production WSGI server like Gunicorn in production')
app.run(debug=False, host='127.0.0.1', port=5000)
# Safe: Docker/deployment configuration
# In docker-compose.yml or deployment scripts:
# environment:
# - FLASK_ENV=production
# - SECRET_KEY=${SECRET_KEY}
# - DATABASE_URL=${DATABASE_URL}
# Safe: Gunicorn configuration (gunicorn.conf.py)
# bind = "0.0.0.0:5000"
# workers = 4
# worker_class = "sync"
# timeout = 30
# keepalive = 2
# max_requests = 1000
# max_requests_jitter = 100
# preload_app = True
# accesslog = "-"
# errorlog = "-"
# loglevel = "info"