secure
HTTP security headers for Python web applications, centered on one object: Secure.
secure exists to keep header policy out of ad hoc view code. Instead of copying header strings into routes, middleware, and framework-specific hooks, you configure one Secure instance and apply it consistently.
Hand-written header code tends to drift. Headers get missed, defaults vary between apps, and sync or async framework details leak into otherwise simple code. secure gives you a small public API, opinionated presets, and typed builders when you need to go beyond the defaults.
Install
secure requires Python 3.10+ and has no external dependencies.
uv add secure
pip install secure
Quick start
Start with Secure.with_default_headers(). It uses Preset.BALANCED, the recommended default for most applications.
from secure import Secure
class Response:
def __init__(self):
self.headers = {}
response = Response()
Secure.with_default_headers().set_headers(response)
Secure applies headers to response objects that expose either:
response.set_header(name, value)response.headers[name] = value
The quick start uses the response.headers[name] = value form.
Use set_headers() for synchronous response objects. Use set_headers_async() in async code or when the response object may use async setters.
Presets
Most applications should start with BALANCED.
from secure import Preset, Secure
balanced = Secure.from_preset(Preset.BALANCED)
basic = Secure.from_preset(Preset.BASIC)
strict = Secure.from_preset(Preset.STRICT)
Preset.BALANCED: recommended default. Modern baseline with CSP, HSTS, referrer policy, permissions policy, and common browser protections.Preset.BASIC: compatibility-oriented. Adds legacy and interoperability headers that some deployments still expect.Preset.STRICT: hardened profile. Tightens CSP, disables caching, and denies framing.
Choose BALANCED unless you have a specific reason to prefer BASIC or STRICT.
Middleware
If your framework supports app-wide middleware, prefer that over setting headers one response at a time.
WSGI
from flask import Flask
from secure import Secure
from secure.middleware import SecureWSGIMiddleware
app = Flask(__name__)
secure_headers = Secure.with_default_headers()
app.wsgi_app = SecureWSGIMiddleware(app.wsgi_app, secure=secure_headers)
ASGI
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 SecureWSGIMiddleware when you can wrap a WSGI app directly. Use SecureASGIMiddleware when you want app-wide coverage in an ASGI stack such as FastAPI, Starlette, or Shiny.
Advanced usage
Most applications can stop at a preset. When you need to tune a specific header, keep Secure as the entry point and pass builder objects into it.
Custom policy
from secure import ContentSecurityPolicy, Secure, StrictTransportSecurity
secure_headers = Secure(
csp=(
ContentSecurityPolicy()
.default_src("'self'")
.img_src("'self'", "https://images.example.com")
.script_src("'self'", "https://cdn.example.com")
),
hsts=StrictTransportSecurity().max_age(63072000).include_subdomains(),
)
Optional validation
The validation pipeline is optional. Use it when headers are being composed dynamically and you want stricter checks before emission.
from secure import Secure
secure_headers = (
Secure.with_default_headers()
.allowlist_headers()
.deduplicate_headers()
.validate_and_normalize_headers()
)
If you need manual emission for an unsupported response contract, iterate over secure_headers.header_items().
Framework examples
See docs/frameworks.md for the full matrix. The most common patterns are:
FastAPI
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)
Flask
from flask import Flask
from secure import Secure
app = Flask(__name__)
secure_headers = Secure.with_default_headers()
@app.after_request
def add_security_headers(response):
secure_headers.set_headers(response)
return response
Starlette
from secure import Secure
from secure.middleware import SecureASGIMiddleware
from starlette.applications import Starlette
app = Starlette()
secure_headers = Secure.with_default_headers()
app.add_middleware(SecureASGIMiddleware, secure=secure_headers)
Links
Release files for secure 2.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| secure-2.0.1.tar.gz | 39.8 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| secure-2.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 93.8 kB
Release files / secure-2.0.1.tar.gz
| Download URL | secure-2.0.1.tar.gz |
|---|---|
| Size | 39.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bb492b9ca2ae66d04f4718675a18de1d1a29217aaded3d6cb5bd8b4ba2e2a645
|
|
BLAKE2b-256 checksum How to use checksums |
ca8884b861346a6a23d75900b493de7458cf89605262e6e19876dc0b576b11ff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.11
|
Release files / secure-2.0.1-py3-none-any.whl
| Download URL | secure-2.0.1-py3-none-any.whl |
|---|---|
| Size | 54.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
12794037e2c4d0b54697045b50285a37faf26ba026cb34a68cd55e96d472898b
|
|
BLAKE2b-256 checksum How to use checksums |
5016b5040aa7644a99f9bd0b08d393b49edaa8d48062ae4757ee107737b60030
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.11.11
|