Security Headers¶
A handful of HTTP response headers tell the browser to lock down behavior
that's otherwise wide open: don't let another site frame this page, don't
guess a response's content type, don't leak the full URL on a cross-origin
navigation, don't restrict which scripts/styles/assets a page is allowed to
load. SecurityHeadersMiddleware adds them; SecurityHeadersServiceProvider
wires it up from .env.
Not registered by default
Unlike CSRF protection or the
WebSocket Origin check. The headers
with a real default here (X-Frame-Options, X-Content-Type-Options,
Referrer-Policy) are safe to turn on for any app, but
Content-Security-Policy genuinely isn't — a policy this framework
picked for you would either be loose enough to be pointless, or break
your own inline scripts, your own CDN-loaded assets, or the Swagger UI
at /docs. Register the provider once you've decided what your own
policy actually is.
Setup¶
# main.py
from zeython import Application, SecurityHeadersServiceProvider
app = Application()
app.register(SecurityHeadersServiceProvider)
Configurable via .env:
SECURITY_HEADERS_CSP— yourContent-Security-Policyvalue. Unset by default; noContent-Security-Policyheader is sent at all until you provide one.SECURITY_HEADERS_FRAME_OPTIONS— defaultDENY(this app can't be framed by another site at all — clickjacking). Set toSAMEORIGINif you legitimately frame your own pages from your own pages.SECURITY_HEADERS_CONTENT_TYPE_OPTIONS— defaulttrue, sendsX-Content-Type-Options: nosniff. Stops a browser from "sniffing" a response's content and running it as something other than its declaredContent-Type— the classic case is an uploaded file served back and executed as HTML instead of staying inert.SECURITY_HEADERS_REFERRER_POLICY— defaultstrict-origin-when-cross-origin: the full URL is sent as theRefereron a same-origin navigation, only the origin cross-origin, and nothing at all on a downgrade from HTTPS to plain HTTP.SECURITY_HEADERS_HSTS— defaultfalse. SendsStrict-Transport-Securityonce enabled, telling the browser to refuse plain HTTP for this host forSECURITY_HEADERS_HSTS_MAX_AGEseconds going forward — including on a link the user typed themselves.SECURITY_HEADERS_HSTS_MAX_AGE— default31536000(1 year).
HSTS is hard to undo once a client has seen it
Only turn this on once every path to this app is actually served over HTTPS. There's no clean way to undo it before the max-age expires for a client that's already seen the header — they'll refuse plain HTTP to this host for the full duration, no matter what you change server-side.
Using the middleware directly¶
Registering via Application.add_middleware skips config entirely — useful
in tests, or if you'd rather set values in code:
from zeython.security_headers import SecurityHeadersMiddleware
app.add_middleware(
SecurityHeadersMiddleware,
content_security_policy="default-src 'self'",
hsts=True,
)
Pass None/False to omit a header entirely rather than send an empty
one — frame_options=None, content_type_options=False, and
referrer_policy=None all work.
Verifying it¶
curl -sI http://localhost:8000/ | grep -Ei '^(x-frame-options|x-content-type-options|referrer-policy|content-security-policy|strict-transport-security):'
With the provider unregistered, none of these appear at all — every response is exactly what it would have been without this module.