1 Commits
Author SHA1 Message Date
jwinterm 51a3371be1 feat(admin,landing): admin-plane hardening + public landing [operator §2]
Admin posture (§2.1/§2.2):
- Hard fail-closed bind guard in main: a non-loopback ADMIN_BIND refuses startup
  unless ADMIN_ALLOW_PUBLIC_BIND=true (was a warning).
- admin_plane_guard middleware on the admin router: Host allowlist
  (localhost/127.0.0.1/[::1]/configured TOR_ADMIN_ONION; a missing Host is allowed
  since rebinding must supply one and the bind is the primary control) to defeat
  DNS-rebinding against the loopback socket, plus X-Frame-Options: DENY +
  CSP frame-ancestors 'none' (anti-clickjacking). Still no CORS on the admin plane.

Public landing (§2.3), default-OFF, one fingerprint surface:
- GET /api/v1/server-info — built from a hand-written ServerInfo allowlist (never
  by serializing config/state). Default projection is minimal { software.name,
  status }; version (coarse major.minor), chains, price_feed, and uptime (bool
  `up` only) are each individually opt-in. The enabled-feature tuple is NEVER
  emitted anonymously (connected clients use /capabilities).
- GET / — minimal HTML (operator title HTML-escaped); bare 404 when off (does not
  hint an admin plane exists). server-info also 404s when off.
- config: ADMIN_ALLOW_PUBLIC_BIND, TOR_ADMIN_ONION; PUBLIC_LANDING_TITLE +
  per-field PUBLIC_EXPOSE_VERSION/CHAINS/PRICE_FEED toggles.

Tests: host_allowlist + html_escape unit tests; landing off->on minimal-projection
(asserts only software+status, no version/chains/features); admin-plane Host-403 +
anti-clickjacking headers. Landing/admin stay off the public OpenAPI.

Deferred (documented): §2.5 NIP-05 consent-gating (changes existing
/.well-known/nostr.json behavior — needs the standing v0.2.4-compat verification
first), §2.3 k-anon public stats, response-header/TLS-fingerprint stripping (proxy
/ops-doc concern).
2026-06-27 15:32:11 -04:00