contracts.rules_cookie_secure

Page actions AI-ready formats and sharing
Open LLM text
Share with AI
Ask Claude Ask ChatGPT Ask Gemini Ask Copilot

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 …

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: a SessionMiddlewareis present but the session cookie it emits is notSecureunder production posture. Store-AGNOSTIC — both CookieSessionStore (data-in-cookie) and RedisSessionStore (session-id-in-cookie) emit aSet-Cookie, so a Secure-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 when samesite=='none' with effective secure==False: browsers silently DROP a SameSite=None cookie that is not Secure, 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 leaves strict_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

_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 (AppConfig field AND any SecurityHeadersMiddlewareconfig), and
  • the app has an auth/mutating surface (reusesis_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