A hardened drop-in replacement for Flask that automatically applies production-grade security defaults. Change one import line and your existing Flask app gets security headers, rate limiting, CSRF protection, secret redaction, input sanitization, and more.
pip install presidio-hardened-flaskBefore (plain Flask):
from flask import Flask, jsonify, request
app = Flask(__name__)
@app.route("/")
def index():
return "Hello, world!"After (presidio-hardened-flask):
from presidio_flask import Flask, jsonify, request
app = Flask(__name__)
@app.route("/")
def index():
return "Hello, secure world!"That's it. Your app now automatically receives:
| Feature | What It Does |
|---|---|
| Security Headers | CSP, HSTS, X-Frame-Options, Permissions-Policy, and more on every response |
| Rate Limiting | 60 req/min per IP with exponential backoff (configurable) |
| CSRF Protection | Sec-Fetch-Site aware + token-based fallback for mutating requests |
| Secret Redaction | Passwords, API keys, and tokens are redacted in logs |
| Session Hardening | Secure, HttpOnly, SameSite=Lax cookies by default |
| Input Sanitization | Blocks SQL injection, XSS, and path traversal attempts |
| CVE Quick-Check | Warns at startup if key dependencies have known vulnerabilities |
| Security Logging | Structured Presidio event logging for all requests |
Plain Flask returns no security headers by default:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
presidio-hardened-flask adds them automatically:
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
Permissions-Policy: geolocation=(), camera=(), microphone=()
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Plain Flask has no built-in rate limiting. A single client can hammer your API.
presidio-hardened-flask enforces per-IP rate limits with informative headers:
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 57
When exceeded, returns 429 Too Many Requests with exponential backoff.
Plain Flask will happily log passwords and tokens in plain text:
app.logger.info("Request: %s", request.json)
# {"username": "alice", "password": "hunter2", "api_key": "sk-live-abc123"}presidio-hardened-flask automatically redacts sensitive fields:
from presidio_flask import redact_dict
print(redact_dict({"password": "hunter2", "api_key": "sk-123"}))
# {"password": "***REDACTED***", "api_key": "***REDACTED***"}Plain Flask has no CSRF protection.
presidio-hardened-flask uses a modern two-layer approach:
- Sec-Fetch-Site header — blocks cross-origin mutating requests automatically
- Token-based fallback — for older browsers or custom setups
from presidio_flask import generate_csrf_token
# In your template / API response:
token = generate_csrf_token()
# Include as X-CSRF-Token header or _csrf_token form fieldAll features are enabled by default but fully configurable:
app = Flask(__name__)
app.config.update(
# Rate limiting
PRESIDIO_RATE_LIMIT=100, # requests per window (default: 60)
PRESIDIO_RATE_WINDOW=120, # window in seconds (default: 60)
PRESIDIO_RATE_LIMIT_ENABLED=True, # disable entirely if False
# CSRF
PRESIDIO_CSRF_ENABLED=True,
# Input sanitization
PRESIDIO_SANITIZE_ENABLED=True,
# Secret redaction
PRESIDIO_REDACTION_ENABLED=True,
# Security logging
PRESIDIO_LOGGING_ENABLED=True,
# CVE check on startup
PRESIDIO_CVE_CHECK=True,
# Custom security headers (merge with/override defaults)
PRESIDIO_SECURITY_HEADERS={
"X-Frame-Options": "SAMEORIGIN",
"Content-Security-Policy": "default-src 'self'; img-src *",
},
)# Clone and install
git clone https://github.com/presidio-security/presidio-hardened-flask.git
cd presidio-hardened-flask
uv venv .venv && source .venv/bin/activate
uv pip install -e ".[dev]"
# Run tests
pytest --cov=presidio_flask
# Lint and format
ruff format .
ruff check . --fix