secure v2.0.1
secure v2.0.1 introduces a cleaner public API, modern preset defaults, first-class ASGI and WSGI middleware, and safer header handling across supported Python web frameworks.
Compared with v1.0.1, v2 keeps the core Secure, with_default_headers(), from_preset(), set_headers(), and set_headers_async() APIs, but changes defaults, adds middleware and normalization helpers, and makes Secure.headers stricter and effectively read-only. Notably, neither v1 nor v2 exposes secure.__version__ as a public attribute, so version checks should use package metadata rather than module attributes.
Highlights
- New preset model with
Preset.BALANCEDas the recommended default - First-class
SecureASGIMiddlewareandSecureWSGIMiddleware - New header pipeline helpers for allowlisting, deduplication, and normalization
- Expanded header coverage, including:
Cross-Origin-Resource-PolicyX-DNS-Prefetch-ControlX-Permitted-Cross-Domain-Policies
- Clearer migration path for users coming from v1 manual response mutation patterns
Key improvements
Middleware
v2 adds framework-agnostic middleware in secure.middleware:
SecureASGIMiddleware(app, *, secure=None, multi_ok=None)SecureWSGIMiddleware(app, *, secure=None, multi_ok=None)
This gives ASGI and WSGI applications a cleaner integration path than manually mutating each response.
Header pipeline helpers
v2 adds new helpers on Secure:
allowlist_headers()deduplicate_headers()validate_and_normalize_headers()header_items()
These make it easier to inspect, normalize, and safely emit headers, especially in applications that may need duplicate-aware handling for multi-valued headers.
New public builders and constants
v2 adds:
CrossOriginResourcePolicyXDnsPrefetchControlXPermittedCrossDomainPoliciesMULTI_OKCOMMA_JOIN_OKDEFAULT_ALLOWED_HEADERS
More predictable header state
v2 tracks headers_list mutations correctly. In v1, once .headers had been read, later mutations to headers_list could leave .headers stale because it was backed by a cached property.
Breaking changes
Secure.headers is stricter
In v1, Secure.headers was a cached dict[str, str], and duplicate header names silently collapsed to the last value.
In v2:
Secure.headersis an immutable mapping- duplicate header names, case-insensitive, raise
ValueError
If your application intentionally or accidentally creates duplicate header names, do not rely on .headers as the source of truth. Use header_items() for ordered inspection, or call deduplicate_headers() before applying headers.
Default headers changed
Secure.with_default_headers() no longer means the same thing it meant in v1.
- In v1,
with_default_headers()includedCache-Control: no-store - In v2,
with_default_headers()maps toPreset.BALANCED, which does not include that same default cache behavior
If you relied on the v1 default cache policy, add it explicitly in v2.
Presets changed materially
v1 included Preset.BASIC and Preset.STRICT.
v2 adds Preset.BALANCED, and Secure.with_default_headers() now maps to that new preset.
This also means Preset.BASIC in v2 is not the old BASIC. Compared with v1 BASIC, v2 BASIC adds or changes:
- COOP
- CORP
- CSP
- HSTS with
includeSubDomains X-DNS-Prefetch-ControlX-Permitted-Cross-Domain-PoliciesOrigin-Agent-ClusterX-Download-OptionsX-XSS-Protection
and drops v1 BASIC defaults such as:
ServerCache-Control
Preset.STRICT also changed:
- v1 STRICT used
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload - v2 STRICT removes
preloadby default - v2 STRICT changes cache control from
no-storetono-store, max-age=0
If you need v1-style HSTS preload behavior, configure it explicitly in v2.
FastAPI integration changed in practice
There is no secure.framework.fastapi helper in either version.
- v1 had no framework module at all
- v2 introduces generic ASGI and WSGI middleware instead
For FastAPI and other ASGI frameworks, the recommended upgrade path is to move from per-response mutation to SecureASGIMiddleware.
Added
SecureASGIMiddlewareSecureWSGIMiddlewareallowlist_headers(...)deduplicate_headers(...)validate_and_normalize_headers(...)header_items()CrossOriginResourcePolicyXDnsPrefetchControlXPermittedCrossDomainPoliciesMULTI_OKCOMMA_JOIN_OKDEFAULT_ALLOWED_HEADERS
Changed
Secure.with_default_headers()now returns the balanced preset- header deduplication and normalization are now first-class operations
- response integration is more robust across sync and async styles
- applications with duplicate header names now fail fast instead of silently collapsing values
- framework guidance now emphasizes middleware-based integration for ASGI and WSGI apps
Migration guide (v1 → v2)
-
Review your defaults
- Do not assume
with_default_headers()in v2 matches v1 - Diff the emitted headers and re-add any defaults you rely on explicitly
- Do not assume
-
Audit any code that reads
Secure.headers- Treat it as read-only in v2
- If duplicate names are possible, use
header_items()or calldeduplicate_headers()first
-
Move ASGI and WSGI apps to middleware
- Replace per-response
set_headers_async()orset_headers()calls withSecureASGIMiddlewareorSecureWSGIMiddleware - Pre-normalize the
Secureinstance if needed before wiring it into middleware
- Replace per-response
FastAPI example
Before, v1-style manual response mutation
from fastapi import FastAPI, Request
from secure import Secure
app = FastAPI()
secure_headers = Secure.with_default_headers()
@app.middleware("http")
async def add_security_headers(request: Request, call_next):
response = await call_next(request)
await secure_headers.set_headers_async(response)
return responseAfter, v2-style ASGI middleware
from fastapi import FastAPI
from secure import Secure
from secure.middleware import SecureASGIMiddleware
app = FastAPI()
secure_headers = (
Secure.with_default_headers()
.deduplicate_headers(action="last")
.validate_and_normalize_headers()
)
app.add_middleware(SecureASGIMiddleware, secure=secure_headers)Upgrade in 3 steps
- Replace any assumption that
with_default_headers()means the same defaults as v1 - Audit code that reads
Secure.headersand update duplicate-header handling - For ASGI and WSGI apps, move from per-response header mutation to middleware with a pre-normalized
Secureinstance
Summary
v2.0.1 keeps the core Secure API intact while making the library safer and easier to integrate in modern Python web applications. The biggest changes are the new preset model, middleware-first integration for ASGI and WSGI frameworks, and stricter handling of duplicate and normalized headers.