ec397f2c65
v0.5 (Live Assist — on-the-job voice companion) milestone complete. 4 phases: P0 (pre-execution, v0.1.10) → P1 (assist core + guardrail, v0.1.11) → P2 (integration + tech-debt + NFR, v0.1.12) → P3 (final review + ship, v0.1.13 = milestone release). 16/16 REQs covered (3 ASSIST + 4 NFR + 9 IDEATE). 4 v0.6 backlog. 469 tests passed, 0 failed. 1 P0 fixed (guardrail processor safety). 8 P1+ flagged for v0.6. 8 v0.4 P1+ tech-debt addressed. G-049 + G-067 grill MUSTs resolved. ESCALATION-01 (PIPEDA) OPEN for human legal review before assist surface go-live. ---ci--- project: praxis phase: 3 milestone: v0.5 status: complete requirements: covered: [REQ-ASSIST-01, REQ-ASSIST-02, REQ-ASSIST-03, REQ-NFR-ASSIST-01, REQ-NFR-ASSIST-02, REQ-NFR-ASSIST-03, REQ-NFR-ASSIST-04, REQ-IDEATE-01, REQ-IDEATE-02, REQ-IDEATE-03, REQ-IDEATE-04, REQ-IDEATE-05, REQ-IDEATE-06, REQ-IDEATE-07, REQ-IDEATE-08, REQ-IDEATE-09] partial: [] ---/ci---
86 lines
3.5 KiB
Python
86 lines
3.5 KiB
Python
"""Signed cookie configuration (TASK-03-02, D-041, D-056, R-AUTH-01, G-031).
|
|
|
|
Returns kwargs for Starlette SessionMiddleware (itsdangerous HMAC-SHA256
|
|
signed cookies — D-056, stateless, no sessions table). The cookie name is
|
|
`praxis_op` (distinct from any future learner cookie).
|
|
|
|
R-AUTH-01 / G-031 reframe: the PRIMARY mitigation for a sniffed operator
|
|
cookie is the k-anonymity defense-in-depth — the cohort dashboard reads
|
|
only k-anonymized aggregates, so a sniffed cookie leaks NO learner PII.
|
|
The `PRAXIS_COOKIE_SECURE` flag is the SECONDARY mitigation (operational
|
|
convenience for when TLS arrives). It defaults to true; the HTTP pilot
|
|
(LXC, no TLS — D-030) sets it to false with a logged WARNING.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import secrets
|
|
|
|
from loguru import logger
|
|
|
|
_COOKIE_MAX_AGE_S = 28800 # 8h (D-041)
|
|
|
|
|
|
def _env_bool(key: str, default: bool) -> bool:
|
|
raw = os.environ.get(key, "").strip().lower()
|
|
if raw in ("true", "1", "yes", "on"):
|
|
return True
|
|
if raw in ("false", "0", "no", "off"):
|
|
return False
|
|
return default
|
|
|
|
|
|
def get_session_middleware_kwargs() -> dict:
|
|
"""Return kwargs for Starlette SessionMiddleware.
|
|
|
|
If PRAXIS_COOKIE_SECRET is unset, generate an ephemeral random secret
|
|
and log a WARNING (dev only — sessions won't survive a restart and this
|
|
MUST NOT be used in pilot/production).
|
|
|
|
TASK-12-02 (P1+ #3 from v0.4 REVIEW): if the secret is set but <32 bytes,
|
|
log a WARNING (the HMAC signature is weakened). The secret is still
|
|
accepted (backward compat — the pilot may have a short secret), but the
|
|
warning is logged. In production (post-pilot), this should be a hard
|
|
error (`raise RuntimeError`). For v0.5 pilot, the warning is sufficient.
|
|
"""
|
|
secret = os.environ.get("PRAXIS_COOKIE_SECRET", "").strip()
|
|
if not secret:
|
|
secret = secrets.token_urlsafe(48)
|
|
logger.warning(
|
|
"PRAXIS_COOKIE_SECRET not set — generated an ephemeral random secret. "
|
|
"Sessions will NOT survive a server restart. This is dev-only; set "
|
|
"PRAXIS_COOKIE_SECRET (>=32 bytes) for pilot/production."
|
|
)
|
|
elif len(secret) < 32:
|
|
# TASK-12-02 (P1+ #3): a short non-empty secret weakens the HMAC
|
|
# signature. Log a WARNING with the remediation guidance. The secret
|
|
# is still accepted (backward compat — pilot); post-pilot this should
|
|
# be a hard error.
|
|
logger.warning(
|
|
"PRAXIS_COOKIE_SECRET is <32 bytes (%d bytes) — HMAC signature weakened. "
|
|
"Use 'openssl rand -base64 48' to generate a >=32-byte secret. "
|
|
"The secret is accepted for pilot (backward compat); post-pilot this "
|
|
"should be a hard error.",
|
|
len(secret),
|
|
)
|
|
secure = _env_bool("PRAXIS_COOKIE_SECURE", True)
|
|
if not secure:
|
|
logger.warning(
|
|
"Cookie Secure flag disabled (PRAXIS_COOKIE_SECURE=false) — HTTP pilot "
|
|
"mode (R-AUTH-01). Do not use in production. NOTE (G-031): the primary "
|
|
"R-AUTH-01 mitigation is k-anon defense-in-depth (cohort dashboard reads "
|
|
"only k-anonymized aggregates → sniffed cookie leaks no PII); this flag "
|
|
"is the secondary mitigation."
|
|
)
|
|
return {
|
|
"secret_key": secret,
|
|
"session_cookie": "praxis_op",
|
|
"max_age": _COOKIE_MAX_AGE_S,
|
|
"https_only": secure,
|
|
"same_site": "strict",
|
|
"path": "/",
|
|
}
|
|
|
|
|
|
__all__ = ["get_session_middleware_kwargs"] |