Implements D-025 (revised 2026-10-15) and D-043.
Setup gate (D-025):
- Remove the bind refusal (CheckSetupBind/ErrNonLoopbackSetup/server.IsLoopback);
Serve binds any address, so the app can listen on 0.0.0.0:8080 behind Zoraxy.
- The gate is now per request and strict (httpx.SetupRequestAllowed): a loopback
TCP peer AND no X-Forwarded-For — the direct SSH tunnel. Anything else is 404
before dispatch; an unparseable peer fails closed (AC-7/AC-10). The raw XFF is
read before ClientIP SETS it (A6).
Proxy scheme (D-043):
- httpx.RequestIsHTTPS resolves the scheme per request: direct TLS, or a trusted
proxy's X-Forwarded-Proto. The door carries it on the request context
(httpx.WithSecure) so the session cookie, its __Host- prefix, the pre-session
and ceremony cookies, and HSTS all agree (AC-13).
- Over plain http (the tunnel) cookies stay bare and non-Secure, which fixes the
bootstrap from an https:// config.example.yaml; Config.IsSecure() is removed
as now dead.
Tests: rewrite the setup-gate tests for AC-7/AC-10, add AC-13, and unit-test
SetupRequestAllowed / RequestIsHTTPS.
REVIEW-001 (M0) M2s: POST /access was a timing oracle — a hit did two
synchronous SMTP sends, a miss one SELECT, so "is this email on record?"
leaked (A3).
Dispatch the notification through a Deps.Dispatch seam (default `go fn()`),
detached from the request context (context.WithoutCancel, so audit values
survive) and bounded by a 30s timeout. Tests inject a synchronous runner
where they assert no-spam semantics, and a new test asserts the default is
asynchronous with a blocking mailer. A panic in the detached goroutine is
recovered: net/http no longer guards it, and a notification must never take
the process down.