docs(plan): record the Zoraxy address (10.1.0.10) in D-043

D-043 now names Zoraxy at 10.1.0.10 (trusted_proxies = [10.1.0.10]); add
the change-log entry for the deployment docs + systemd unit slice.
This commit is contained in:
Claus Lohmar 2026-10-15 06:15:44 +00:00
parent f58d33aee1
commit bd7c9ca567

View file

@ -476,7 +476,7 @@ the top admin (first user)
| **D-040** | 2026-10-13 | **Agent-workflow variables live in `.opencode/git.env`** (gitignored), with a committed `.opencode/git.env.example`. Git-native settings (identity · remote · branch · pull) live in repo-local `.git/config`. | Two-tier config (SPEC-001 A-04): git owns what git owns; automation knobs stay out of git. | | **D-040** | 2026-10-13 | **Agent-workflow variables live in `.opencode/git.env`** (gitignored), with a committed `.opencode/git.env.example`. Git-native settings (identity · remote · branch · pull) live in repo-local `.git/config`. | Two-tier config (SPEC-001 A-04): git owns what git owns; automation knobs stay out of git. |
| **D-041** | 2026-10-14 | **The setup wizard enrolls TOTP only.** Any **TOTP login with no passkey** is **nudged** to enroll one, with *not now* / ***don't ask again*** (`users.passkey_opt_out`). The nudge is **suppressed while the origin is loopback** (or in setup mode). | Resolves **O-13**: a passkey enrolled over loopback binds RP ID `127.0.0.1` and is unusable at the public domain. Passkeys are encouraged, not forced — TOTP stays a valid factor (D-005). | | **D-041** | 2026-10-14 | **The setup wizard enrolls TOTP only.** Any **TOTP login with no passkey** is **nudged** to enroll one, with *not now* / ***don't ask again*** (`users.passkey_opt_out`). The nudge is **suppressed while the origin is loopback** (or in setup mode). | Resolves **O-13**: a passkey enrolled over loopback binds RP ID `127.0.0.1` and is unusable at the public domain. Passkeys are encouraged, not forced — TOTP stays a valid factor (D-005). |
| **D-042** | 2026-10-14 | **TOTP-only enrollment is specific to the bootstrap admin (`/setup`).** **Invited users enroll both factors** (Passkey + TOTP) at the real origin; the passkey **nudge** remains the safety net for any identity without a passkey. | Resolves **O-14** (owner, Option A): keep the **strongest default** where the origin permits it. | | **D-042** | 2026-10-14 | **TOTP-only enrollment is specific to the bootstrap admin (`/setup`).** **Invited users enroll both factors** (Passkey + TOTP) at the real origin; the passkey **nudge** remains the safety net for any identity without a passkey. | Resolves **O-14** (owner, Option A): keep the **strongest default** where the origin permits it. |
| **D-043** | 2026-10-15 | **Deployment: behind Zoraxy.** The app listens on **`0.0.0.0:8080` over HTTP**; **TLS terminates at Zoraxy**; the public origin is **`https://myday.sechpoint.app`** (`base_url`). `trusted_proxies` = Zoraxy. The app **honours `X-Forwarded-Proto`** from the proxy to set `Secure` / `__Host-` cookies and HSTS. | The app is **not** the TLS endpoint, so it must honour the proxy's scheme header. **Firewall `:8080` to Zoraxy only.** | | **D-043** | 2026-10-15 | **Deployment: behind Zoraxy.** The app listens on **`0.0.0.0:8080` over HTTP**; **TLS terminates at Zoraxy** at **`10.1.0.10`**; the public origin is **`https://myday.sechpoint.app`** (`base_url`). `trusted_proxies` = `["10.1.0.10"]` (Zoraxy). The app **honours `X-Forwarded-Proto`** from the proxy to set `Secure` / `__Host-` cookies and HSTS. | The app is **not** the TLS endpoint, so it must honour the proxy's scheme header. **Firewall `:8080` to Zoraxy only.** |
--- ---
@ -643,4 +643,6 @@ shell/layout lives in `core/web`; a module's views live with the module.
| 2026-10-14 | **REVIEW-001 FIX PASS (security).** **H2** — a **per-request** loopback gate in `dispatch` while in setup mode (a non-loopback *resolved* client IP → 404; the bind check alone is defeated by a reverse proxy — the exact first-visitor-wins D-025 claims to close); SPEC-003 §7 + AC-10, A13 → **enforced**. **H1** — `ClientIP` walks XFF **right→left** and returns the first untrusted hop, falling back to the peer (A6/AC-12); the unit test that encoded the vulnerable expectation is rewritten. **M1c/L3s** — the `setup` **module is pinned at the door**: `serveSetupAction` rejects `Module != "setup"`, and any other resolution naming it 404s in every mode instead of falling through to `serveGUID`. **M1s** — the CSRF/cookie signing keys are **generated at startup** in setup mode (mirroring the TOTP key, D-1/R-6), so the wizard's ceremony is never signed with a shipped public placeholder; `finish` persists the same values. **M2s** — the `/access` notification runs **off the request path** (a `Dispatch` seam, `go fn()` by default, context detached) so a hit is not timed against a miss (A3). **M3s** — the rate limiter's state map is **pruned** (time-gated sweep at a size threshold) and **capped** (memory-DoS). **L1s** — the session cookie is **`__Host-myday_session`** over https (conditional: a browser rejects `__Host-` without `Secure`, which would break the http loopback bootstrap). **L5s** — **TOTP replay** refused: the highest accepted counter per identity is tracked (reset on re-enrollment). **L4s** — `Permissions-Policy` · `Cross-Origin-Opener-Policy` · `Cross-Origin-Resource-Policy`. **L7c** — the nudge's origin signal is the configured **`base_url` host** (falling back to the request `Host`), so a proxy rewriting `Host` cannot suppress it. SPEC-003 amended (§4/§6/§7/§9/§10 + §11 the fix table). | | 2026-10-14 | **REVIEW-001 FIX PASS (security).** **H2** — a **per-request** loopback gate in `dispatch` while in setup mode (a non-loopback *resolved* client IP → 404; the bind check alone is defeated by a reverse proxy — the exact first-visitor-wins D-025 claims to close); SPEC-003 §7 + AC-10, A13 → **enforced**. **H1** — `ClientIP` walks XFF **right→left** and returns the first untrusted hop, falling back to the peer (A6/AC-12); the unit test that encoded the vulnerable expectation is rewritten. **M1c/L3s** — the `setup` **module is pinned at the door**: `serveSetupAction` rejects `Module != "setup"`, and any other resolution naming it 404s in every mode instead of falling through to `serveGUID`. **M1s** — the CSRF/cookie signing keys are **generated at startup** in setup mode (mirroring the TOTP key, D-1/R-6), so the wizard's ceremony is never signed with a shipped public placeholder; `finish` persists the same values. **M2s** — the `/access` notification runs **off the request path** (a `Dispatch` seam, `go fn()` by default, context detached) so a hit is not timed against a miss (A3). **M3s** — the rate limiter's state map is **pruned** (time-gated sweep at a size threshold) and **capped** (memory-DoS). **L1s** — the session cookie is **`__Host-myday_session`** over https (conditional: a browser rejects `__Host-` without `Secure`, which would break the http loopback bootstrap). **L5s** — **TOTP replay** refused: the highest accepted counter per identity is tracked (reset on re-enrollment). **L4s** — `Permissions-Policy` · `Cross-Origin-Opener-Policy` · `Cross-Origin-Resource-Policy`. **L7c** — the nudge's origin signal is the configured **`base_url` host** (falling back to the request `Host`), so a proxy rewriting `Host` cannot suppress it. SPEC-003 amended (§4/§6/§7/§9/§10 + §11 the fix table). |
| 2026-10-14 | **REVIEW-001 FIX PASS (correctness & cleanup).** **L1c/L2s** — dead `secrets.session_key` **removed** (`config.go` · `setup.go` · `config.example.yaml`; SPEC-003 D-2 amended). **L8c** — README corrected (M0/M0.1 status; `/setup` **persists `config.yaml`** and a **restart adopts the generated secrets**; the per-request loopback rule stated). **M2c** — an automated **restart-survival test** (`cmd/myday/main_test.go`) generates the TOTP key at startup, enrolls a secret, persists via the wizard, reopens the DB with the persisted key and asserts the secret still unseals — the **D-1** invariant, previously covered only by a manual smoke. Gate green (`build` · `vet` · `test -race`), **static CGO-free** binary, `routing` purity intact; the H2 guard was reverted to confirm its test fails without it. **Still open (deferred, low): L3c · L4c · L5c · L6c.** | | 2026-10-14 | **REVIEW-001 FIX PASS (correctness & cleanup).** **L1c/L2s** — dead `secrets.session_key` **removed** (`config.go` · `setup.go` · `config.example.yaml`; SPEC-003 D-2 amended). **L8c** — README corrected (M0/M0.1 status; `/setup` **persists `config.yaml`** and a **restart adopts the generated secrets**; the per-request loopback rule stated). **M2c** — an automated **restart-survival test** (`cmd/myday/main_test.go`) generates the TOTP key at startup, enrolls a secret, persists via the wizard, reopens the DB with the persisted key and asserts the secret still unseals — the **D-1** invariant, previously covered only by a manual smoke. Gate green (`build` · `vet` · `test -race`), **static CGO-free** binary, `routing` purity intact; the H2 guard was reverted to confirm its test fails without it. **Still open (deferred, low): L3c · L4c · L5c · L6c.** |
| 2026-10-15 | **Deployment topology (D-043) + D-025 REVISED.** The app sits **behind Zoraxy** (`myday.sechpoint.app`); **TLS terminates at the proxy**, so the app **listens on `0.0.0.0:8080` over HTTP** — a **bind refusal is impossible**. **D-025 becomes a per-request gate**: in setup mode serve only a request whose **TCP peer is loopback *and* which carries no `X-Forwarded-For`** (the **direct SSH tunnel**); anything else → 404. **D-043:** honour the proxy's **`X-Forwarded-Proto`** (`Secure` + `__Host-` cookies, HSTS); `trusted_proxies` = Zoraxy; **firewall `:8080` to Zoraxy only**. SPEC-003 §4/§6/§7/§9 amended (AC-7 · AC-10 · **AC-13**). | | 2026-10-15 | **Deployment topology (D-043) + D-025 REVISED.** The app sits **behind Zoraxy** (`myday.sechpoint.app`); **TLS terminates at the proxy**, so the app **listens on `0.0.0.0:8080` over HTTP** — a **bind refusal is impossible**. **D-025 becomes a per-request gate**: in setup mode serve only a request whose **TCP peer is loopback *and* which carries no `X-Forwarded-For`** (the **direct SSH tunnel**); anything else → 404. **D-043:** honour the proxy's **`X-Forwarded-Proto`** (`Secure` + `__Host-` cookies, HSTS); `trusted_proxies` = Zoraxy; **firewall `:8080` to Zoraxy only**. SPEC-003 §4/§6/§7/§9 amended (AC-7 · AC-10 · **AC-13**). |
| 2026-10-15 | **D-043 follow-ups (rulings).** **B1 → accepted (add):** a **startup warning** when `base_url` is `https://` but `trusted_proxies` is empty/wrong — otherwise `X-Forwarded-Proto` is ignored and the session cookie is silently **not** `Secure`. **B2 accepted** — `X-Forwarded-Proto`'s leftmost token is correct for a single Zoraxy; a multi-proxy chain would need the same right→left walk as XFF. **B3 accepted** — the tunnel gate trusts any **local** connection (inherent to "local = trusted"; D-025). Deviations **D1–D5 accepted**, incl. D4 (no redirect code was needed — redirects are relative and absolute URLs come from `base_url`). **Deployment requirement:** Zoraxy **must set `X-Forwarded-For`** — the setup gate's "no XFF" test depends on it. |
| 2026-10-15 | **D-025 (revised) + D-043 ✅ IMPLEMENTED.** **Bind refusal removed** — `server.CheckSetupBind` / `ErrNonLoopbackSetup` / `server.IsLoopback` deleted; `Serve` binds any address (the deployment binds `0.0.0.0:8080`). **Setup gate is per request and strict** — `httpx.SetupRequestAllowed` requires a **loopback TCP peer AND no `X-Forwarded-For`**, evaluated on the RAW request (captured before `ClientIP` SETS the header); anything else → **404 before dispatch**; an unparseable peer fails closed (AC-7/AC-10). **Scheme is per request (D-043)** — `httpx.RequestIsHTTPS` (direct TLS, or a **trusted** proxy's `X-Forwarded-Proto`), carried on the request context (`httpx.WithSecure`), so the session cookie, the **`__Host-`** prefix, the pre-session/ceremony cookies and **HSTS** all agree; the plain-http tunnel keeps the **bare, non-Secure** cookie — which also fixes the latent `config.example.yaml` bootstrap (an `https://` `base_url` no longer force-marks the cookie Secure over the tunnel). `config.IsSecure` **removed** (dead). `config.example.yaml` documents the Zoraxy topology + the **firewall-`:8080`-to-Zoraxy-only** rule; the README gains a **Deployment** section and the tunnel-only bootstrap. Syncs **SPEC-003 §4/§6/§7/§9** and **SPEC-005 §3/§5/§6**. Gate green (`build` · `vet` · `test -race` — 18 pkgs) · **static CGO-free** binary · `routing` purity intact. Live smoke (real binary): `/setup` via the tunnel → **200**, `+ X-Forwarded-For` → **404**, `/home` → **404**, `X-Forwarded-Proto: https` → **HSTS + Secure** cookie, plain http → **neither**. | | 2026-10-15 | **D-025 (revised) + D-043 ✅ IMPLEMENTED.** **Bind refusal removed** — `server.CheckSetupBind` / `ErrNonLoopbackSetup` / `server.IsLoopback` deleted; `Serve` binds any address (the deployment binds `0.0.0.0:8080`). **Setup gate is per request and strict** — `httpx.SetupRequestAllowed` requires a **loopback TCP peer AND no `X-Forwarded-For`**, evaluated on the RAW request (captured before `ClientIP` SETS the header); anything else → **404 before dispatch**; an unparseable peer fails closed (AC-7/AC-10). **Scheme is per request (D-043)** — `httpx.RequestIsHTTPS` (direct TLS, or a **trusted** proxy's `X-Forwarded-Proto`), carried on the request context (`httpx.WithSecure`), so the session cookie, the **`__Host-`** prefix, the pre-session/ceremony cookies and **HSTS** all agree; the plain-http tunnel keeps the **bare, non-Secure** cookie — which also fixes the latent `config.example.yaml` bootstrap (an `https://` `base_url` no longer force-marks the cookie Secure over the tunnel). `config.IsSecure` **removed** (dead). `config.example.yaml` documents the Zoraxy topology + the **firewall-`:8080`-to-Zoraxy-only** rule; the README gains a **Deployment** section and the tunnel-only bootstrap. Syncs **SPEC-003 §4/§6/§7/§9** and **SPEC-005 §3/§5/§6**. Gate green (`build` · `vet` · `test -race` — 18 pkgs) · **static CGO-free** binary · `routing` purity intact. Live smoke (real binary): `/setup` via the tunnel → **200**, `+ X-Forwarded-For` → **404**, `/home` → **404**, `X-Forwarded-Proto: https` → **HSTS + Secure** cookie, plain http → **neither**. |
| 2026-10-15 | **Deployment docs for D-043 (Zoraxy).** Added **`docs/deploy-zoraxy.md`** (topology · the app `config.yaml` · the Zoraxy **ProxyHost** with `X-Forwarded-For` + `X-Forwarded-Proto` · the **firewall `:8080` from `10.1.0.10` only** · the **tunnel-only bootstrap** + the restart that adopts the persisted secrets · the **systemd** install · the **misconfig warnings**) and **`deploy/myday.service`** (a `Type=simple` · `Restart=on-failure` unit with `User=`/`WorkingDirectory=`/`ExecStart=`/`After=network-online.target`). `config.example.yaml` now sets **`trusted_proxies: ["10.1.0.10"]`** (Zoraxy) with the silent-failure + firewall caveats; the README Deployment section points at the guide. **Docs/deploy only — no code change**; gate green. |