Define HTTP security headers once. Apply them consistently across Python web apps.
secure provides a small, dependency-free API for configuring modern security headers across common Python web frameworks through ASGI middleware, WSGI middleware, or framework response hooks.
Use it when you want to avoid copy-pasted header strings spread across handlers, hooks, and middleware.
Quick links: Quick start · Headers · Middleware
Setting headers manually is fine for one endpoint. It gets harder to review when values are copied across routes, response hooks, reverse-proxy settings, and different framework integrations.
secure helps you:
- Keep one
Securepolicy object instead of scattered header strings - Apply the same policy through ASGI middleware, WSGI middleware, or response hooks
- Start with practical presets, then customize headers for your application
- Use builders for complex headers such as Content Security Policy and Permissions Policy
- Make duplicate handling, header overwrites, and validation explicit
The defaults are a reasonable starting point, not a substitute for application-specific review. Content Security Policy in particular should be adjusted for the scripts, styles, assets, and third-party services your app actually uses.
uv add secure
# or
pip install secureUse middleware when you can attach secure once and cover the whole application.
from fastapi import FastAPI
from secure import Secure
from secure.middleware import SecureASGIMiddleware
app = FastAPI()
secure_headers = Secure.with_default_headers()
app.add_middleware(SecureASGIMiddleware, secure=secure_headers)Use the same policy object in framework hooks, middleware callbacks, or handlers that expose a response object with headers support.
from secure import Secure
secure_headers = Secure.with_default_headers()
secure_headers.set_headers(response)
# or
await secure_headers.set_headers_async(response)Secure.with_default_headers() maps to Preset.BALANCED, a practical default designed to be customized.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Resource-Policy: same-origin
Content-Security-Policy: default-src 'self'; base-uri 'self'; font-src 'self' https: data:; form-action 'self'; frame-ancestors 'self'; img-src 'self' data:; object-src 'none'; script-src 'self'; script-src-attr 'none'; style-src 'self' https: 'unsafe-inline'; upgrade-insecure-requests
Strict-Transport-Security: max-age=31536000; includeSubDomains
Permissions-Policy: geolocation=(), microphone=(), camera=()
Referrer-Policy: strict-origin-when-cross-origin
X-Content-Type-Options: nosniff
X-Frame-Options: SAMEORIGINThis preset reflects modern browser guidance from MDN and OWASP. It reduces cross-origin risk, prevents MIME sniffing, and provides a conservative Content Security Policy you can extend.
The default CSP avoids unsafe script execution by default, but CSP is context-sensitive. Interactive apps, dashboards, CDNs, analytics, embedded content, or other third-party integrations may require additional configuration.
from secure import Preset, Secure
Secure.from_preset(Preset.BALANCED)
Secure.from_preset(Preset.BASIC)
Secure.from_preset(Preset.STRICT)- BALANCED: practical default for many applications
- BASIC: Helmet-style compatibility
- STRICT: tighter CSP and isolation
Start with BALANCED, review the generated headers, and move stricter only when needed.
secure provides both ASGI and WSGI middleware.
- Overwrites headers by default
- Allows controlled duplication via
multi_ok - Only modifies HTTP responses in ASGI apps
from secure import Secure
from secure.middleware import SecureASGIMiddleware
secure_headers = Secure.with_default_headers()
app = SecureASGIMiddleware(app, secure=secure_headers)from secure import Secure
from secure.middleware import SecureWSGIMiddleware
secure_headers = Secure.with_default_headers()
app = SecureWSGIMiddleware(app, secure=secure_headers)from secure import Secure
class SecureHeadersMiddleware:
def __init__(self, get_response):
self.get_response = get_response
self.secure = Secure.with_default_headers()
def __call__(self, request):
response = self.get_response(request)
self.secure.set_headers(response)
return responseThese examples show common integration paths. See the full framework integration guide for additional frameworks and notes about first-class middleware versus minimal fallback examples.
from secure.middleware import SecureASGIMiddleware
app.add_middleware(SecureASGIMiddleware, secure=secure_headers)@app.after_request
def add_security_headers(response):
secure_headers.set_headers(response)
return responsefrom shiny import App
from secure.middleware import SecureASGIMiddleware
app = SecureASGIMiddleware(App(), secure=secure_headers)Interactive apps often need CSP configuration for their script, style, and asset loading patterns.
from secure.headers import ContentSecurityPolicy
csp = (
ContentSecurityPolicy()
.default_src("'self'")
.script_src("'self'", "cdn.example.com")
)from secure.headers import PermissionsPolicy
permissions = (
PermissionsPolicy()
.geolocation("'self'")
.camera("'none'")
)Use the optional pipeline when you need stricter control:
secure_headers = (
Secure.with_default_headers()
.allowlist_headers(...)
.deduplicate_headers(...)
.validate_and_normalize_headers(...)
)This makes header allowlists, deduplication, normalization, and validation explicit instead of leaving those choices spread across application code.
- Python 3.10+
- No external dependencies
Read the full documentation.
MIT License
Issues and pull requests are welcome.
Built using guidance from MDN and OWASP secure headers recommendations.
