Missing Security Headers in Flask
Flask sets no security headers by default. As a microframework, it leaves all security configuration to the developer. Flask-Talisman is the standard solution for adding Content-Security-Policy, Strict-Transport-Security, and other headers, but many Flask applications are deployed without it.
Scan Your Flask AppHow Missing Security Headers Manifests in Flask
A default Flask application sends only Content-Type and Content-Length headers. Every security header — CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy — must be added explicitly. Developers who use Flask for APIs often assume security headers do not matter for JSON responses. While CSP has no effect on API-only responses, headers like Strict-Transport-Security and X-Content-Type-Options still protect the transport layer and prevent MIME sniffing of error pages. Flask blueprints can complicate header management. Headers set in an @after_request handler on the main app may not apply to blueprint routes if the blueprint has its own error handlers that return responses before the app-level handler runs. Flask applications behind reverse proxies (Nginx, Gunicorn) sometimes rely on the proxy to set security headers. This works but creates a configuration gap — headers are missing in development and may be silently lost if the proxy configuration changes.
Real-World Impact
A Flask application rendered user-generated markdown using the markdown library and Jinja2 templates. Without CSP headers, a reflected XSS payload in a preview endpoint ran unrestricted, hijacking admin sessions. Flask-Talisman with a default CSP would have blocked the inline script. A Flask API was deployed without HSTS. An attacker performed an SSL stripping attack on a coffee shop Wi-Fi network, intercepting API tokens sent over the downgraded HTTP connection.
Step-by-Step Fix
Install and configure Flask-Talisman
Flask-Talisman adds all essential security headers with sensible defaults.
# pip install flask-talisman
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
csp = {
'default-src': "'self'",
'script-src': "'self'",
'style-src': "'self' 'unsafe-inline'",
'img-src': "'self' data: https:",
'font-src': "'self'",
'frame-ancestors': "'none'",
}
Talisman(
app,
content_security_policy=csp,
strict_transport_security=True,
strict_transport_security_max_age=31536000,
strict_transport_security_include_subdomains=True,
force_https=True, # Set to False in development
)Add headers without Flask-Talisman
If you prefer not to use Flask-Talisman, set headers manually with an after_request handler.
from flask import Flask
app = Flask(__name__)
@app.after_request
def set_security_headers(response):
response.headers['Content-Security-Policy'] = (
"default-src 'self'; script-src 'self'; "
"style-src 'self' 'unsafe-inline'; frame-ancestors 'none'"
)
response.headers['Strict-Transport-Security'] = (
'max-age=31536000; includeSubDomains; preload'
)
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin'
response.headers['Permissions-Policy'] = (
'camera=(), microphone=(), geolocation=()'
)
return responseHandle different environments
Disable HTTPS enforcement and adjust CSP during local development.
import os
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
is_production = os.environ.get('FLASK_ENV') == 'production'
csp = {
'default-src': "'self'",
'script-src': "'self'",
'style-src': "'self' 'unsafe-inline'",
}
Talisman(
app,
content_security_policy=csp,
force_https=is_production,
strict_transport_security=is_production,
session_cookie_secure=is_production,
)Prevention Best Practices
1. Install Flask-Talisman to add all essential security headers in one step. 2. Customize CSP directives for your application's specific needs. 3. Set force_https=True in production to ensure HTTPS enforcement. 4. Add Permissions-Policy manually since Flask-Talisman does not include it. 5. Verify headers in both development and production environments. 6. Set headers in Flask, not only in the reverse proxy, for defense in depth.
How to Test
1. Run curl -I http://localhost:5000/ and check for missing security headers. 2. Verify headers on all route types: pages, API endpoints, error pages, and static files. 3. Check that Flask-Talisman is initialized before any routes are registered. 4. Test CSP by injecting an inline script and verifying the browser blocks it. 5. Use Vibe App Scanner to automatically detect missing security headers in your Flask application.
Frequently Asked Questions
Does Flask set any security headers by default?
No. Flask is a microframework that sets no security headers at all by default. You must add every header — CSP, HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy, and Permissions-Policy — either manually via @after_request or by using Flask-Talisman.
Should I use Flask-Talisman or set headers manually?
Use Flask-Talisman for most projects. It handles CSP, HSTS, session cookie security, HTTPS enforcement, and most security headers in a single configuration. Manual @after_request handlers are error-prone and must be maintained as best practices evolve.
Will Flask-Talisman break my development environment?
Yes, if you leave force_https=True while running on localhost without HTTPS. Set force_https=False for development. You can use FLASK_ENV or a custom environment variable to toggle production-only settings like HTTPS enforcement and HSTS.
Related Security Resources
Is Your Flask App Vulnerable to Missing Security Headers?
VAS automatically scans for missing security headers vulnerabilities in Flask applications and provides step-by-step remediation guidance with code examples.
Scans from $5, results in minutes. Get actionable fixes tailored to your Flask stack.