When deploying Flask applications to production, security headers are essential for protecting against common web vulnerabilities. Flask-Talisman makes it easy to implement enterprise-grade security headers.
Why Security Headers Matter
Modern web applications face numerous security threats:
- XSS (Cross-Site Scripting): Malicious scripts injected into trusted websites
- Clickjacking: Tricking users into clicking on hidden elements
- Man-in-the-Middle Attacks: Intercepting communication between client and server
- Content Sniffing: Browsers executing malicious content
Security headers provide defense-in-depth against these attacks.
Installing Flask-Talisman
pip install Flask-Talisman
Basic Implementation
Here's a minimal Flask-Talisman setup:
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
# Enable Talisman with default settings
Talisman(app)
@app.route('/')
def index():
return 'Secure Flask App'
if __name__ == '__main__':
app.run()
This automatically adds:
Strict-Transport-Security(HSTS)X-Content-Type-Options: nosniffX-Frame-Options: SAMEORIGINContent-Security-Policy
Configuring Content Security Policy (CSP)
CSP is the most powerful security header, controlling which resources can be loaded:
from flask import Flask, render_template
from flask_talisman import Talisman
app = Flask(__name__)
# Define CSP policy
csp = {
'default-src': "'self'",
'script-src': [
"'self'",
"'unsafe-inline'", # Allow inline scripts (use carefully!)
"https://cdn.jsdelivr.net",
"https://cdn.tailwindcss.com"
],
'style-src': [
"'self'",
"'unsafe-inline'",
"https://cdn.jsdelivr.net"
],
'img-src': [
"'self'",
"data:",
"https:"
],
'font-src': [
"'self'",
"data:",
"https://fonts.gstatic.com"
],
'connect-src': "'self'",
'frame-ancestors': "'none'",
'base-uri': "'self'",
'form-action': "'self'"
}
Talisman(app, content_security_policy=csp)
CSP Directives Explained
HSTS (HTTP Strict Transport Security)
HSTS forces browsers to use HTTPS:
Talisman(app,
force_https=True,
strict_transport_security=True,
strict_transport_security_max_age=31536000, # 1 year
strict_transport_security_include_subdomains=True,
strict_transport_security_preload=True
)
⚠️ Important: Only enable
preloadif you're ready to submit your domain to the HSTS Preload List.
Environment-Based Configuration
Don't enforce HTTPS in development:
import os
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
# Only enable Talisman in production
if os.environ.get('FLASK_ENV') == 'production':
Talisman(app,
content_security_policy=csp,
force_https=True
)
else:
# Disable HTTPS redirect in development
Talisman(app,
content_security_policy=csp,
force_https=False
)
Advanced Configuration: Per-Route Policies
Different routes may need different CSP policies:
from flask import Flask
from flask_talisman import Talisman
app = Flask(__name__)
talisman = Talisman(app)
@app.route('/')
def index():
return 'Home Page'
@app.route('/embed')
@talisman(
frame_options='ALLOW-FROM',
frame_options_allow_from='https://trusted-site.com',
content_security_policy={
'default-src': "'self'",
'frame-ancestors': ["'self'", "https://trusted-site.com"]
}
)
def embed():
return 'Embeddable Content'
@app.route('/api/data')
@talisman(content_security_policy=None) # Disable CSP forAPI endpointdef api_data():
return {'data': 'value'}
Feature Policy / Permissions Policy
Control browser features:
Talisman(app,
feature_policy={
'geolocation': "'none'",
'camera': "'none'",
'microphone': "'none'",
'payment': "'self'"
}
)
Testing Your Security Headers
Use these tools to verify your headers:
1. Command Line Testing
# Check headers with curl
curl -I https://your-app.com
# Expected output includes:
# Strict-Transport-Security: max-age=31536000; includeSubDomains
# X-Content-Type-Options: nosniff
# X-Frame-Options: SAMEORIGIN
# Content-Security-Policy: default-src 'self'
2. Python Testing
import requests
def test_security_headers():
response = requests.get('https://your-app.com')
headers = response.headers
assert 'Strict-Transport-Security' in headers
assert 'X-Content-Type-Options' in headers
assert headers['X-Content-Type-Options'] == 'nosniff'
assert 'Content-Security-Policy' in headers
print("✓ All security headers present")
test_security_headers()
3. Online Tools
Common Issues and Solutions
Issue 1: CSP Blocking Inline Scripts
Problem: Your inline JavaScript is blocked by CSP.
Solution: Use nonces or move scripts to external files:
from flask import render_template
import secrets
@app.route('/')
def index():
nonce = secrets.token_urlsafe(16)
# Pass nonce to template
return render_template('index.html', csp_nonce=nonce)
# In template:
# <script nonce="{{ csp_nonce }}">
# console.log('This script is allowed');
# </script>
Issue 2: Third-Party CDNs Blocked
Problem: External CDN resources are blocked.
Solution: AddCDN domains to your CSP:
csp = {
'script-src': [
"'self'",
"https://cdn.jsdelivr.net",
"https://cdnjs.cloudflare.com"
]
}
Production Checklist
- [ ] Enable HSTS with appropriate max-age
- [ ] Configure CSP without
unsafe-inline(if possible) - [ ] Test CSP in report-only mode first
- [ ] Use
X-Frame-Options: DENYorSAMEORIGIN - [ ] Enable
X-Content-Type-Options: nosniff - [ ] Set
Referrer-Policyappropriately - [ ] Configure Feature Policy/Permissions Policy
- [ ] Test all pages for CSP violations
- [ ] Monitor CSP reports in production
Conclusion
Flask-Talisman is an essential tool for securing Flask applications in production. By implementing proper security headers, you protect your users from common web vulnerabilities and demonstrate security best practices.
Remember: Security is not a one-time setup. Regularly review and update your security policies as your application evolves.