Cookie-hardening contract checks — Secure session cookies + HSTS nudge.
Two env-aware, deploy-escalating rules that guard the cookie-transport posture
the runtime now resolves at freeze (resolve_cookie_secure/
SessionMiddleware.secure). Both mirror rules_security_stack/
rules_safety: middleware is detected by class name (never isinstance,
never importing middleware into the contracts layer), severity is read from
config.env so chirp check --deployescalates via the production-posture
config view, and the check shares ONE source of truth with the runtime by
importingresolve_cookie_secure from chirp.middleware.sessions.
Categories:
-
cookie_secure: aSessionMiddlewareis present but the session cookie it emits is notSecureunder production posture. Store-AGNOSTIC — bothCookieSessionStore(data-in-cookie) andRedisSessionStore(session-id-in-cookie) emit aSet-Cookie, so aSecure-less Redis session-id cookie is equally exploitable (sniffable over a plaintext path → session hijack). Severity is ERROR in production, WARNING in staging, silent in development. A second, env-independent ERROR fires whensamesite=='none'with effectivesecure==False: browsers silently DROP aSameSite=Nonecookie that is notSecure, so the session breaks in every environment — a correctness footgun, not just a hardening gap. -
hsts: in production, an app with an auth/mutating surface that leavesstrict_transport_securityunset gets a WARNING + docs nudge. HSTS is NOT auto-emitted andstrict_transport_security=Noneis NOT overloaded to mean "auto" —Nonestays "off". An HSTS header is an irreversible multi-year browser pin; emitting it on a declared-env guess (the app may be reached over plain HTTP behind a misconfigured proxy) is worse than the gap. So this is a WARNING only — never promoted to ERROR, never an auto-injected header.
contracts.rules_cookie_secure
| Name | Type | Default | Description |
|---|---|---|---|
type
|
|
— | |
qualified_name
|
|
— | |
element_type
|
|
— | |
description
|
|
— | |
source_file
|
|
— | |
line_number
|
|
— | |
is_autodoc
|
|
— | |
autodoc_element
|
|
— | |
_autodoc_template
|
|
— | |
_autodoc_url_path
|
|
— | |
_autodoc_page_type
|
|
— | |
title
|
|
— | |
doc_content_hash
|
|
— |
Symbols on this page
Resolve the effectiveSecureflag of a SessionMiddleware's cookie.
Read the originally-configured value via theconfigured_secure
accessor — the unresolved"auto"| bool —…
Read the effectivesamesiteof the cookie this middleware emits.
Reads the store's config when present (the store owns the cookie attributes), falling back…
Flag a non-Securesession cookie under production posture.
No-op when noSessionMiddleware is registered — security_stackalready
owns the presence check. When a …
True when HSTS is effectively set, anywhere it can be configured.
HSTS lives in two places:AppConfig.strict_transport_security(which the
compiler auto-wires into aSecurityHeadersMiddleware…
Nudge for missing HSTS on a production app with an auth/mutating surface.
WARNING (never ERROR) when all hold:
env == 'production'(HSTS is meaningless…
_effective_secure
function
def _effective_secure(mw: Any, env: str) -> bool
Resolve the effectiveSecureflag of a SessionMiddleware's cookie.
Read the originally-configured value via theconfigured_secure
accessor — the unresolved"auto"| bool — and re-resolve it through the
sharedresolve_cookie_secure() against the posture env. This is the
crux of correct--deploy behavior: the compiler resolves "auto"to a
concrete bool AT FREEZE using the real (often development) env, so reading
the freeze-resolvedsecure would burn the "auto"sentinel and falsely
ERROR a deploy-ready default app under production posture. Re-resolving the
configured value against the posture env evaluates the app as it WOULD be
in that env ("auto" → Secure in production). Falls back to secure
then"auto"for a custom middleware without the accessor.
resolve_cookie_secure is imported lazily: chirp.middleware.sessions
pulls initsdangerous (the optional sessionsextra) at module load,
and the contracts layer must import cleanly without it. The import only runs
when aSessionMiddlewareis actually present, by which point the sessions
machinery is necessarily installed.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
mw
|
Any
|
— | |
env
|
str
|
— |
_session_samesite
function
def _session_samesite(mw: Any) -> str
Read the effectivesamesiteof the cookie this middleware emits.
Reads the store's config when present (the store owns the cookie attributes),
falling back to the middleware's own config. Defaults to"lax"(the
SessionConfigdefault) when neither is readable.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
mw
|
Any
|
— |
check_cookie_secure
function
_hsts_configured
function
def _hsts_configured(config: Any, middleware_list: list[Any]) -> bool
True when HSTS is effectively set, anywhere it can be configured.
HSTS lives in two places:AppConfig.strict_transport_security(which the
compiler auto-wires into aSecurityHeadersMiddlewarein production-with-TLS)
AND aSecurityHeadersConfig.strict_transport_securityon a hand-added
SecurityHeadersMiddleware. Treat either as "set" so a user who configures
HSTS only on their own middleware does not get a false WARNING.
Also treat HSTS as configured when the compiler will auto-wire it: in
production withssl_certfileset (TLS terminated in-process), the
compiler appends aSecurityHeadersMiddlewareemitting HSTS at runtime
even thoughstrict_transport_securityis unset here — so nudging would
be a false positive for a correctly-configured TLS app.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
config
|
Any
|
— | |
middleware_list
|
list[Any]
|
— |
check_hsts
function
def check_hsts(router: Router, config: Any, middleware_list: list[Any], discovered_routes: list[Any] | None = None) -> list[ContractIssue]
Nudge for missing HSTS on a production app with an auth/mutating surface.
WARNING (never ERROR) when all hold:
env == 'production'(HSTS is meaningless in dev; a guess in staging is not nudged either — only the declared production posture),strict_transport_securityis unset everywhere it can be configured (AppConfigfield AND anySecurityHeadersMiddlewareconfig), and- the app has an auth/mutating surface (reuses
is_mutating_route() — the same predicatesecurity_stackowns).
Deliberately a WARNING + docs nudge ONLY. Chirp does NOT auto-emit an HSTS
header from a declared-env guess and does NOT overload
strict_transport_security=None to mean "auto": Nonestays "off". HSTS
is an irreversible multi-year browser pin — emitting it becauseenvsays
production (while the app may actually be reached over plain HTTP behind a
misconfigured proxy) is worse than the gap.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
router
|
Router
|
— | |
config
|
Any
|
— | |
middleware_list
|
list[Any]
|
— | |
discovered_routes
|
list[Any] | None
|
None
|
View source · /home/runner/work/chirp/chirp/site/../src/chirp/contracts/rules_cookie_secure.py:1