The real topology is an LXC container (11020) behind Proxmox + Zoraxy, so
the SSH tunnel is impractical: it lands on the container gateway, not
loopback. D-025 is re-revised and D-044 adds the general gate.
- core/setup: a Gate (gate.go) holding the 128-bit setup code (generated at
first run, or pinned by config `setup_code`) plus a signed unlock cookie
(`myday_setup`) that carries a keyed one-way FINGERPRINT of the code, never
the code itself. The code is single-use (retired on the first success) and
the comparison ignores case/separators.
- The unlock action (`POST /setup?do=setup&action=unlock`) is CSRF-protected
(A15), rate-limited (the /setup A3 class) and audited; a wrong or absent
code gets the same generic 403 page.
- The door (server.Deps.SetupGate): in setup mode the wizard is served to
EITHER the direct tunnel (gate a) OR an unlocked request (gate b); `GET /`
and an un-unlocked `GET /setup` render a friendly first-run page with the
code form — never a bare 404 — and every other route stays 404.
- cmd/myday prints an unmistakable startup banner: setup mode, the wizard
URL, the code, where to read it (journalctl / pct exec 11020) and the
restart that adopts the persisted secrets.
- httpx.Signer.Fingerprint backs the unlock cookie; config gains `setup_code`.
Tests cover the boilerplate at `/` and `/setup` (200, not 404), the unlock
(code → wizard; wrong/absent → generic, 429 under the limit), single-use, the
CSRF requirement, access-log redaction, and that a non-/setup route is still
404. Gate green (build · vet · test -race); live-smoked against the binary.
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) perimeter findings:
- H1: resolveForwarded walked X-Forwarded-For left→right and returned the
leftmost untrusted entry. An append-style proxy (nginx's default) appends
the peer it saw to the RIGHT of whatever the client sent, so the leftmost
entry is attacker-controlled — a fresh rate-limit bucket per request (A3)
and a forged client_ip in the audit trail (A11/D-031). Walk right→left and
return the first untrusted entry, falling back to the peer.
- L1s: the session cookie now carries the `__Host-` prefix when it is Secure
(https), so it is pinned to the exact origin and cannot be shadowed from a
sibling subdomain. Conditional on purpose: a browser rejects a `__Host-`
cookie without Secure, which would break the http loopback bootstrap.
- L4s: add Permissions-Policy, Cross-Origin-Opener-Policy and
Cross-Origin-Resource-Policy.
- Add httpx.IsLoopbackIP: a RESOLVED client IP test that fails closed on an
empty/unparseable value (the setup-mode gate, H2), sharing a classifier
with IsLoopbackHost whose "unknown" direction is the opposite, each
fail-closed for its own decision.
Tests updated: the unit test encoding the vulnerable leftmost expectation is
rewritten, plus right→left/forged-entry/host-classification coverage.
- store: SQLite (WAL, foreign keys ON, CGO-free driver) + the embedded
migration runner; each module's migrations are name-prefixed (D-012)
- audit: the append-only audit log; the actor threads via context (R-1)
- users: the user_id/GUID/email anchors (D-013)
- config: the YAML settings file (D-035) + validation
- httpx: cookies · loopback detection · trusted-proxy XFF · CSRF · security
headers · redaction
- middleware: correlation id · audit · rate limiting
- mail: the invite/recovery mailer seam
- server: the core DOOR — the resolution switch, the dispatch registry
(RegisterModule), the role gates, /setup · enroll/login · recovery flows,
the reserved `static` workflow, and the loopback-only bootstrap (D-025)