Files
gatehouse-api/gatehouse_app/middleware/security_headers.py
T

76 lines
2.7 KiB
Python
Raw Normal View History

2026-01-08 01:00:26 +10:30
"""Security headers middleware."""
import os
from flask import current_app, request
2026-01-08 01:00:26 +10:30
class SecurityHeadersMiddleware:
"""Middleware to add security headers to responses."""
def __init__(self, app=None):
"""Initialize middleware."""
self.app = app
if app is not None:
self.init_app(app)
def init_app(self, app):
"""Initialize with Flask app."""
app.after_request(self.add_security_headers)
@staticmethod
def add_security_headers(response):
"""Add security headers to response."""
# Prevent MIME type sniffing
response.headers["X-Content-Type-Options"] = "nosniff"
# Enable XSS protection
response.headers["X-XSS-Protection"] = "1; mode=block"
# Prevent clickjacking
response.headers["X-Frame-Options"] = "DENY"
# Strict Transport Security (HSTS)
if request.is_secure:
response.headers["Strict-Transport-Security"] = (
"max-age=31536000; includeSubDomains"
)
# Content Security Policy
try:
flask_env = current_app.config.get("ENV") or os.environ.get("FLASK_ENV", "production")
if flask_env == "development":
connect_src = "connect-src 'self' http://localhost:5000 http://127.0.0.1:5000"
else:
connect_src = "connect-src 'self'"
except RuntimeError:
connect_src = "connect-src 'self'"
2026-01-08 01:00:26 +10:30
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self' 'unsafe-inline'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"font-src 'self' data:; "
+ connect_src
2026-01-08 01:00:26 +10:30
)
# Referrer Policy
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
# Permissions Policy
response.headers["Permissions-Policy"] = (
"geolocation=(), microphone=(), camera=()"
)
# Cache-Control: Allow OIDC endpoints to set their own Cache-Control
# Only set no-cache for API responses that haven't set their own cache headers
if "Cache-Control" not in response.headers:
# Check if this is a JSON API response (shouldn't be cached)
content_type = response.headers.get("Content-Type", "")
if "application/json" in content_type:
response.headers["Cache-Control"] = "no-cache, no-store"
elif "text/html" not in content_type:
# For non-HTML responses, add Pragma for HTTP/1.0 compatibility
response.headers["Pragma"] = "no-cache"
2026-01-08 01:00:26 +10:30
return response