opensourceprojects.dev

A broadsheet for software that doesn't ask for your email

Define HTTP security headers once, apply them across Python web apps
GitHub RepoImpressions3

Project Description

View on GitHub

Stop Copy-Pasting Security Headers Across Your Python Apps

You've probably done it. You set up X-Frame-Options in one handler, added Strict-Transport-Security in a middleware, and then spent twenty minutes hunting down where the Content-Security-Policy string actually lives. Header configuration has a way of spreading across routes, response hooks, reverse-proxy configs, and framework-specific integrations until nobody's quite sure what's being applied where. The secure package is a small Python library that tries to fix that by letting you define your HTTP security headers once and apply them consistently across frameworks.

What It Does

secure gives you a single Secure policy object that holds your security header configuration. Instead of scattering header strings across your codebase, you build one policy and then apply it wherever you need it—through ASGI middleware, WSGI middleware, or framework response hooks.

The library is dependency-free and works across common Python web frameworks. You can attach it as middleware when you want blanket coverage for an entire application, or use the same policy object in framework hooks and handlers that expose a response object with header support. The API includes both synchronous and asynchronous methods (set_headers and set_headers_async), so it fits into either style of application.

It also ships with presets—BALANCED, BASIC, and STRICT—and includes builders for more complex headers like Content Security Policy and Permissions Policy.

Why It's Cool

  • One policy object, multiple integration points. The core design idea here is that your header configuration shouldn't care how it gets applied. You define a Secure instance once and then plug it into ASGI middleware, WSGI middleware, or response hooks depending on what your app needs. That's a genuinely useful separation, especially if you're running multiple services with different frameworks.

  • The defaults are actually reasonable. The BALANCED preset (which is what Secure.with_default_headers() gives you) sets Cross-Origin-Opener-Policy, Cross-Origin-Resource-Policy, a conservative Content Security Policy, Strict-Transport-Security, Permissions-Policy, Referrer-Policy, X-Content-Type-Options, and X-Frame-Options. That's a solid starting point, and the README is upfront that it reflects current guidance from MDN and OWASP rather than being something the author invented.

  • It's honest about the limits of defaults. The README explicitly says the defaults are "a reasonable starting point, not a substitute for application-specific review" and calls out that CSP in particular needs adjustment for the scripts, styles, assets, and third-party services your app actually uses. That kind of honesty is refreshing—security libraries that pretend a single preset solves everything tend to cause more problems than they fix.

  • Explicit handling of the fiddly parts. The README mentions that duplicate handling, header overwrites, and validation are made explicit. Anyone who's debugged a header being set twice by different layers of middleware knows why that matters.

  • No dependencies. For a security-focused library, keeping the dependency surface at zero is a meaningful choice. Less to audit, less to break.

How to Try It

Install it with uv or pip:

uv add secure
# or
pip install secure

Then, if you're using FastAPI or another ASGI framework, attach the middleware once:

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)

If you'd rather apply headers at the response level—in a hook, a callback, or a handler—you can reuse the same policy object:

from secure import Secure

secure_headers = Secure.with_default_headers()

secure_headers.set_headers(response)
# or
await secure_headers.set_headers_async(response)

To start from a different preset, use Secure.from_preset() with Preset.BALANCED, Preset.BASIC, or Preset.STRICT. The project lives at github.com/typeerror/secure, where you'll find the full documentation and the rest of the preset details.

Final Thoughts

secure isn't trying to be a security framework—it's solving a narrower problem: keeping header configuration in one place and applying it consistently. That's a good scope. If you're maintaining a single small app with one or two endpoints, you might not need this. But if you've got multiple Python services, mixed ASGI and WSGI stacks, or you've ever had to grep through a repo to figure out which layer is setting Content-Security-Policy, this library is worth a look. The presets give you a sane starting point, and the explicit handling of overwrites and validation means you won't be surprised by what ends up in your responses.


Follow @githubprojects for more developer tools and open source projects.

Back to Projects
Project ID: 42f7f313-4718-44b1-9665-f9be5c295c0fLast updated: September 18, 2026 at 02:45 AM