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.
fromfastapiimportFastAPIfromsecureimportSecurefromsecure.middlewareimportSecureASGIMiddlewareapp=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.
fromsecureimportSecuresecure_headers=Secure.with_default_headers()
secure_headers.set_headers(response)
# orawaitsecure_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-originCross-Origin-Resource-Policy: same-originContent-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-requestsStrict-Transport-Security: max-age=31536000; includeSubDomainsPermissions-Policy: geolocation=(), microphone=(), camera=()Referrer-Policy: strict-origin-when-cross-originX-Content-Type-Options: nosniffX-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.
fromsecureimportPreset, SecureSecure.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
fromsecureimportSecurefromsecure.middlewareimportSecureASGIMiddlewaresecure_headers=Secure.with_default_headers()
app=SecureASGIMiddleware(app, secure=secure_headers)fromsecureimportSecurefromsecure.middlewareimportSecureWSGIMiddlewaresecure_headers=Secure.with_default_headers()
app=SecureWSGIMiddleware(app, secure=secure_headers)fromsecureimportSecureclassSecureHeadersMiddleware:
def__init__(self, get_response):
self.get_response=get_responseself.secure=Secure.with_default_headers()
def__call__(self, request):
response=self.get_response(request)
self.secure.set_headers(response)
returnresponseThese examples show common integration paths. See the full framework integration guide for additional frameworks and notes about first-class middleware versus minimal fallback examples.
fromsecure.middlewareimportSecureASGIMiddlewareapp.add_middleware(SecureASGIMiddleware, secure=secure_headers)@app.after_requestdefadd_security_headers(response):
secure_headers.set_headers(response)
returnresponsefromshinyimportAppfromsecure.middlewareimportSecureASGIMiddlewareapp=SecureASGIMiddleware(App(), secure=secure_headers)Interactive apps often need CSP configuration for their script, style, and asset loading patterns.
fromsecure.headersimportContentSecurityPolicycsp= (
ContentSecurityPolicy()
.default_src("'self'")
.script_src("'self'", "cdn.example.com")
)fromsecure.headersimportPermissionsPolicypermissions= (
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.
