Files
responder/tests/test_security_headers.py
kennethreitz e0717e32ba v5.2: auth ext + OpenAPI security schemes + security-headers middleware
- responder.ext.auth: BearerAuth/BasicAuth/APIKeyAuth schemes usable as
  dependencies (inject the principal) or called directly; sync/async verify,
  constant-time static-secret paths, correct 401 + WWW-Authenticate.
- OpenAPI security: api.add_security_scheme()/scheme.register(api) populate
  components.securitySchemes (Swagger Authorize button); security= on routes/
  verb decorators marks per-op requirements; default=True requires globally.
- API(security_headers=True) + responder.middleware.SecurityHeadersMiddleware:
  opt-in nosniff/X-Frame-Options/Referrer-Policy (+ optional CSP/Permissions),
  preserving handler-set headers.
- DI: callable instances with async __call__ are now awaited (not run in a
  thread), which is what lets an auth scheme object be a dependency.

Backwards-compatible. 490 tests pass; ruff + mypy clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-28 20:02:35 -04:00

65 lines
1.8 KiB
Python

"""v5.2: opt-in SecurityHeadersMiddleware."""
from starlette.testclient import TestClient
import responder
from responder.middleware import SecurityHeadersMiddleware
def _client(api):
return TestClient(api, base_url="http://;")
def _app(**kwargs):
api = responder.API(
allowed_hosts=[";"], secret_key="x" * 32, session_https_only=False, **kwargs
)
@api.route("/")
def home(req, resp):
resp.text = "ok"
return api
def test_off_by_default():
r = _client(_app()).get("/")
assert "x-content-type-options" not in r.headers
def test_defaults_when_enabled():
r = _client(_app(security_headers=True)).get("/")
assert r.headers["x-content-type-options"] == "nosniff"
assert r.headers["x-frame-options"] == "DENY"
assert r.headers["referrer-policy"] == "strict-origin-when-cross-origin"
assert "content-security-policy" not in r.headers # CSP is opt-in
def test_dict_config_adds_csp():
api = _app(security_headers={"content_security_policy": "default-src 'self'"})
r = _client(api).get("/")
assert r.headers["content-security-policy"] == "default-src 'self'"
assert r.headers["x-content-type-options"] == "nosniff"
def test_handler_set_header_is_preserved():
api = responder.API(
allowed_hosts=[";"], secret_key="x" * 32, session_https_only=False,
security_headers=True,
)
@api.route("/")
def home(req, resp):
resp.headers["X-Frame-Options"] = "SAMEORIGIN"
resp.text = "ok"
assert _client(api).get("/").headers["x-frame-options"] == "SAMEORIGIN"
def test_installable_via_add_middleware():
api = _app()
api.add_middleware(SecurityHeadersMiddleware, headers={"X-Test": "1"})
r = _client(api).get("/")
assert r.headers["x-test"] == "1"
assert r.headers["x-content-type-options"] == "nosniff"