This checklist covers Flask-specific security configurations, Python best practices, and Heroku deployment security for Flask applications. Note: Flask is a microframework - you must add most security features yourself.
Expedited WAF for Flask
Expedited WAF adds network level request filtering, preventing malicious attacks from ever touching your application. This enables true defense in depth for your Flask app—even if your framework has a vulnerability or is improperly configured, Expedited WAF dynamically blocks threats before they reach your code.
From experience, we’ve seen many applications where attacks technically didn’t succeed, but burned through so many server resources that the application went down anyway. By filtering malicious requests at the network edge, Expedited WAF protects both your security and your availability.
New Security Features for Your Flask App
These capabilities aren’t available out of the box with Flask, but Expedited WAF adds them instantly:
- Block IP Addresses - Stop malicious actors by IP or CIDR range
- Block User Agents - Filter out bad bots and scrapers
- Block by Geolocation - Restrict traffic by country or region
- DDoS Protection - Stop attacks with CAPTCHA challenges
- Block Anonymous Proxies - Prevent traffic from VPNs and proxy networks
Flask Core Security
-
Configure SECRET_KEY securely
- Store in Heroku config:
heroku config:set SECRET_KEY='your-secret-key' - Load from environment:
import os app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY')- Use cryptographically strong random string (50+ characters)
- Never hardcode or commit to Git
- Store in Heroku config:
-
Disable DEBUG mode in production
app.config['DEBUG'] = os.environ.get('DEBUG', 'False') == 'True'- Verify with
heroku config:get DEBUG - Never deploy with
debug=True
- Verify with
-
Configure secure session cookies
app.config['SESSION_COOKIE_SECURE'] = True # HTTPS only app.config['SESSION_COOKIE_HTTPONLY'] = True # No JavaScript access app.config['SESSION_COOKIE_SAMESITE'] = 'Strict' app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=24) -
Implement CSRF protection
- Install Flask-WTF:
pip install Flask-WTF
from flask_wtf.csrf import CSRFProtect csrf = CSRFProtect(app) app.config['WTF_CSRF_ENABLED'] = True app.config['WTF_CSRF_TIME_LIMIT'] = None # Or set specific timeout- Use
{{ form.csrf_token }}in all forms
- Install Flask-WTF:
-
Add security headers manually
- Install Flask-Talisman:
pip install flask-talisman
from flask_talisman import Talisman Talisman(app, force_https=True, strict_transport_security=True, content_security_policy={ 'default-src': "'self'", 'script-src': "'self'", 'style-src': "'self'" } ) - Install Flask-Talisman:
-
Implement HTTPS/SSL enforcement
from flask import request @app.before_request def before_request(): if request.url.startswith('http://'): url = request.url.replace('http://', 'https://', 1) return redirect(url, code=301)- Or use Flask-Talisman (recommended)
Input Validation & Output Encoding
-
Validate all user input
- Use Flask-WTF forms with validators
- Never trust user input
from wtforms import StringField from wtforms.validators import DataRequired, Length, Email class MyForm(FlaskForm): email = StringField('Email', validators=[DataRequired(), Email()]) -
Escape output to prevent XSS
- Jinja2 auto-escapes by default - keep it enabled
- Never use
| safeunless absolutely necessary - Sanitize user-generated HTML with Bleach:
import bleach clean_html = bleach.clean(user_input) -
Prevent template injection
- Never render user input directly as templates
- Avoid
render_template_string()with user data
Database Security
-
Use parameterized queries
- With SQLAlchemy (recommended):
from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy(app) # Always use ORM or parameterized queries User.query.filter_by(email=user_email).first()- Never string concatenation for SQL
-
Configure Heroku Postgres with SSL
import os from urllib.parse import urlparse database_url = os.environ.get('DATABASE_URL') if database_url and database_url.startswith('postgres://'): database_url = database_url.replace('postgres://', 'postgresql://', 1) app.config['SQLALCHEMY_DATABASE_URI'] = database_url app.config['SQLALCHEMY_ENGINE_OPTIONS'] = { 'connect_args': { 'sslmode': 'require' } } -
Don’t log SQL queries in production
app.config['SQLALCHEMY_ECHO'] = False app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
Authentication & Authorization
-
Use established authentication libraries
- Flask-Login for session-based auth
- Flask-JWT-Extended for API tokens
- Never roll your own
-
Implement Flask-Login properly
from flask_login import LoginManager, login_required login_manager = LoginManager(app) login_manager.login_view = 'login' login_manager.session_protection = 'strong' -
Hash passwords securely
- Use Flask-Bcrypt or Werkzeug:
from werkzeug.security import generate_password_hash, check_password_hash hashed = generate_password_hash(password, method='pbkdf2:sha256') check_password_hash(hashed, password) -
Implement rate limiting
- Install Flask-Limiter:
pip install Flask-Limiter
from flask_limiter import Limiter limiter = Limiter(app, key_func=lambda: request.remote_addr) @app.route('/login', methods=['POST']) @limiter.limit("5 per minute") def login(): pass - Install Flask-Limiter:
-
Protect sensitive routes
from flask_login import login_required @app.route('/admin') @login_required def admin(): pass
File Uploads & Static Files
-
Validate file uploads
ALLOWED_EXTENSIONS = {'txt', 'pdf', 'png', 'jpg', 'jpeg'} def allowed_file(filename): return '.' in filename and \ filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS- Check file extension and MIME type
- Limit file size
- Generate unique filenames
-
Store uploads externally
- Never store on Heroku’s ephemeral filesystem
- Use S3 with Flask-S3 or boto3
- Set appropriate permissions
-
Serve static files securely
- Use WhiteNoise for production
- Configure proper cache headers
- Don’t serve from user-writable directories
CORS & API Security
-
Configure CORS properly
- Install Flask-CORS:
pip install Flask-CORS
from flask_cors import CORS CORS(app, resources={ r"/api/*": { "origins": ["https://yourdomain.com"], "methods": ["GET", "POST"], "allow_headers": ["Content-Type"] } })- Never use
CORS(app)without restrictions in production
- Install Flask-CORS:
-
Implement API authentication
- Use Flask-JWT-Extended for tokens
- Implement proper token expiration
- Validate all API inputs
-
Add API rate limiting
- Apply to all endpoints
- Different limits for authenticated vs anonymous
Python Dependencies
-
Pin all dependency versions
pip freeze > requirements.txt- Use exact versions in production
- Review dependencies regularly
-
Scan for vulnerabilities
pip install pip-audit pip-audit- Run in CI/CD pipeline
- Keep dependencies updated
-
Remove unused dependencies
- Minimize attack surface
- Use lightweight alternatives when possible
Environment Variables & Configuration
-
Use environment variables for all config
class Config: SECRET_KEY = os.environ.get('SECRET_KEY') DATABASE_URL = os.environ.get('DATABASE_URL') MAIL_SERVER = os.environ.get('MAIL_SERVER') app.config.from_object(Config) -
Store secrets in Heroku config vars
heroku config:set SECRET_KEY='...' heroku config:set DATABASE_URL='...' -
Never commit secrets to Git
- Use
.gitignorefor.envfiles - Use python-decouple for local development
- Use
Logging & Error Handling
-
Configure production logging
import logging from logging.handlers import RotatingFileHandler if not app.debug: handler = logging.StreamHandler() handler.setLevel(logging.INFO) app.logger.addHandler(handler) -
Implement custom error pages
@app.errorhandler(404) def not_found(error): return render_template('404.html'), 404 @app.errorhandler(500) def internal_error(error): db.session.rollback() # If using database return render_template('500.html'), 500 -
Never expose stack traces
- Custom error pages for all errors
- Log errors server-side only
-
Don’t log sensitive data
- Passwords, tokens, API keys
- Personal information
- Credit card data
Heroku Deployment
-
Use production WSGI server
# Procfile web: gunicorn app:app --log-file -- Install Gunicorn:
pip install gunicorn - Configure workers:
--workers=4 - Enable access logs
- Install Gunicorn:
-
Specify Python runtime
# runtime.txt python-3.11.7- Use supported versions only
- Keep runtime updated
Testing & Deployment
-
Test on staging environment
heroku create myapp-staging git push staging main- Test all security configurations
- Verify HTTPS enforcement
- Test authentication flows
-
Write security tests
- Test authentication/authorization
- Test CSRF protection
- Test input validation
-
Review before deployment
- Check all config vars are set
- Verify DEBUG=False
- Test all security headers
- Review dependencies
Flask Extensions for Security
Essential Security Extensions:
Flask-WTF- CSRF protection and formsFlask-Talisman- Security headers and HTTPSFlask-Login- User session managementFlask-Bcrypt- Password hashingFlask-Limiter- Rate limitingFlask-CORS- CORS configurationFlask-SeaSurf- Additional CSRF protection