BCDRPulse

Security Headers Checker / Headers / Frameworks

Security headers on Django

A baseline set of security headers for Django: HSTS, a Content-Security-Policy in Report-Only mode to start, framing protection, nosniff, a private Referrer-Policy, a Permissions-Policy and the cross-origin headers. Check what each can break below before you deploy, then scan your site to confirm.

The headers

# yourapp/middleware.py, then add "yourapp.middleware.security_headers" to MIDDLEWARE in settings.py.
# (Settings already cover some: SECURE_HSTS_SECONDS, SECURE_CONTENT_TYPE_NOSNIFF, SECURE_REFERRER_POLICY, X_FRAME_OPTIONS, SECURE_CROSS_ORIGIN_OPENER_POLICY.)
def security_headers(get_response):
    def middleware(request):
        response = get_response(request)
        response["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
        response["Content-Security-Policy-Report-Only"] = "default-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'self'; form-action 'self'; upgrade-insecure-requests"
        response["X-Frame-Options"] = "SAMEORIGIN"
        response["X-Content-Type-Options"] = "nosniff"
        response["Referrer-Policy"] = "strict-origin-when-cross-origin"
        response["Permissions-Policy"] = "camera=(), microphone=(), geolocation=(), payment=(), usb=(), browsing-topics=()"
        response["Cross-Origin-Opener-Policy"] = "same-origin-allow-popups"
        response["Cross-Origin-Resource-Policy"] = "same-site"
        return response
    return middleware

Will this break anything?

  • Strict-Transport-Security: once a browser has seen it, it refuses plain HTTP for this host for max-age seconds (and with includeSubDomains for every subdomain: mail, staging, old tools). Check they all serve HTTPS, start with max-age=300, then raise it. preload is very hard to undo.
  • Content-Security-Policy-Report-Only blocks nothing: browsers only report what an enforced policy would block, so it is safe to add first and watch.
  • X-Frame-Options: DENY stops every site from framing yours, including your own other domains, booking or payment widgets that frame your pages, and page-builder previews. SAMEORIGIN allows your own origin.
  • X-Content-Type-Options: browsers refuse scripts and stylesheets served with the wrong Content-Type (a script sent as text/plain): check your files are served with correct types.
  • Referrer-Policy: sites you link to see less about where visitors came from. strict-origin-when-cross-origin still sends your origin, which most referral reports need; no-referrer sends nothing.
  • Permissions-Policy: a feature set to () is off for the page and every frame in it, including embedded maps, videos, payment buttons and chat widgets. Check what your page and its embeds use first.
  • Cross-Origin-Opener-Policy: same-origin breaks pop-ups that talk back to your page (OAuth sign-in, PayPal and some payment windows); same-origin-allow-popups keeps them working.
  • Cross-Origin-Resource-Policy: same-site or same-origin stops other sites from embedding your files (images, fonts, scripts). Files other sites embed on purpose need cross-origin.

Redirect HTTP to HTTPS

# settings.py
SECURE_SSL_REDIRECT = True
# behind a proxy: SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")

Cookie flags

# settings.py
SESSION_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = "Lax"
CSRF_COOKIE_SECURE = True

Then check the result

The checker grades what browsers actually apply and shows each fix for Django.

Open the Security Headers Checker