Release v0.27.0: security hardening (audit 0026)
Remediates the rfc-app application + deploy-config findings from the Session 0026 security audit. Cut as the "v0.25.0-security-hardening" branch (from v0.24.0); reversioned to 0.27.0 on rebase onto main since v0.26.0 (#28) shipped while this was in flight. - C1 (Critical): single sanitizeHtml.js chokepoint (DOMPurify) for every marked→innerHTML / dangerouslySetInnerHTML sink (MarkdownPreview, ProposalView x2, Editor); rel=noopener hook on target=_blank links. - H1: per-account OTC-verify lockout (migration 023, auto-applied) + per-IP throttle via new ratelimit.py; wired on otc verify/request + passcode check/verify. - M1: device_trust.lookup() single indexed-row read — cookie value is now "<row_id>.<raw_token>"; bcrypt-checks one row, not a global table scan. (Behavior change: existing device-trust cookies re-prompt once.) - M2: HTTP security headers (CSP/HSTS/XFO/XCTO/Referrer-Policy) at nginx. - M4: session cookie Secure-by-default (SESSION_COOKIE_SECURE opt-out). - M5: bounce webhook fails CLOSED (503) when secret unset, instead of open; RFC_APP_INSECURE_BOUNCE_WEBHOOK=1 dev opt-in. - L2/L3: per-IP cooldown + check-endpoint throttle. - L4: systemd sandbox knobs. L8/I1: nginx server_tokens off + TLS1.0/1.1 out. VERSION + frontend/package.json → 0.27.0; CHANGELOG documents the upgrade steps (incl. the out-of-band nginx + systemd apply, which the flotilla deploy gesture does not perform). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -557,7 +557,26 @@ def make_router(config: Config) -> APIRouter:
|
||||
# stays unauthenticated for dev (the v1 contract).
|
||||
import os as _os
|
||||
expected = _os.environ.get("WEBHOOK_EMAIL_BOUNCE_SECRET", "").strip()
|
||||
if expected:
|
||||
# v0.25.0 (audit 0026 M5): fail closed. An unset secret used to
|
||||
# leave this endpoint fully unauthenticated — anyone could suppress
|
||||
# any user's mail by POSTing their address (email_opt_out_all flip
|
||||
# below). Now an unset secret DISABLES the endpoint (503) instead
|
||||
# of opening it. A dev that genuinely wants it open opts in
|
||||
# explicitly with RFC_APP_INSECURE_BOUNCE_WEBHOOK=1, mirroring the
|
||||
# RFC_APP_INSECURE_WEBHOOKS dev-bypass on the Gitea hook.
|
||||
if not expected:
|
||||
if _os.environ.get("RFC_APP_INSECURE_BOUNCE_WEBHOOK", "").strip() == "1":
|
||||
log.warning(
|
||||
"email-bounce webhook running UNAUTHENTICATED "
|
||||
"(RFC_APP_INSECURE_BOUNCE_WEBHOOK=1) — never set this in production"
|
||||
)
|
||||
else:
|
||||
log.error(
|
||||
"email-bounce webhook refused: WEBHOOK_EMAIL_BOUNCE_SECRET is unset "
|
||||
"(set the secret to enable, or RFC_APP_INSECURE_BOUNCE_WEBHOOK=1 for dev)"
|
||||
)
|
||||
raise HTTPException(503, "Bounce webhook not configured")
|
||||
else:
|
||||
received = request.headers.get("X-Webhook-Secret", "")
|
||||
import hmac as _hmac
|
||||
if not received or not _hmac.compare_digest(expected, received):
|
||||
|
||||
Reference in New Issue
Block a user