Files
Idle-Fantasy-Save-Viewer/security.py
T
elpatron 58b9e0bb0a Harden app for production behind nginx Proxy Manager.
Remove path-based import, add rate limits and upload caps, security headers, proxy trust, bundled Chart.js, non-root Docker, and NPM deployment docs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-19 16:23:27 +02:00

63 lines
2.0 KiB
Python

"""Application security: limits, proxy trust, headers."""
from __future__ import annotations
import os
from flask import Flask, request
from flask_limiter import Limiter
from flask_limiter.util import get_remote_address
from werkzeug.middleware.proxy_fix import ProxyFix
limiter = Limiter(key_func=get_remote_address, storage_uri="memory://")
DEFAULT_MAX_UPLOAD_MB = 10
VIEWER_CREATE_LIMIT = os.environ.get("RATE_LIMIT_VIEWER_CREATE", "5 per minute")
IMPORT_LIMIT = os.environ.get("RATE_LIMIT_IMPORT", "20 per hour")
def local_viewer_disabled() -> bool:
return os.environ.get("DISABLE_LOCAL_VIEWER", "").lower() in ("1", "true", "yes")
def trust_proxy_enabled() -> bool:
return os.environ.get("TRUST_PROXY", "").lower() in ("1", "true", "yes")
def configure_app(flask_app: Flask) -> None:
max_mb = int(os.environ.get("MAX_UPLOAD_MB", DEFAULT_MAX_UPLOAD_MB))
flask_app.config["MAX_CONTENT_LENGTH"] = max_mb * 1024 * 1024
if trust_proxy_enabled():
flask_app.wsgi_app = ProxyFix(
flask_app.wsgi_app,
x_for=1,
x_proto=1,
x_host=1,
)
limiter.init_app(flask_app)
@flask_app.after_request
def _security_headers(response):
response.headers["X-Frame-Options"] = "SAMEORIGIN"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data:; "
"connect-src 'self'; "
"frame-ancestors 'self'"
)
return response
def external_base_url() -> str:
"""Build public base URL (respects reverse proxy and PREFERRED_URL_SCHEME)."""
preferred = os.environ.get("PREFERRED_URL_SCHEME", "").strip()
if preferred:
return f"{preferred}://{request.host}"
return request.host_url.rstrip("/")