Files
buzz/docs/admin/README.md
Eli FosterandClaude Opus 5 057c716700 fix(relay): disable admin dashboard on bad config instead of failing startup
BUZZ_ADMIN_HOST with a missing or invalid password aborted relay startup.
bb-public sets BUZZ_ADMIN_HOST=admin.buzz.xyz in production with no
password configured, so shipping this would have crashlooped the whole
relay fleet — websocket, media, git, everything — over an optional
dashboard.

Log at error! and leave the admin surface off instead. `admin = None`
means is_admin_host() is false, the admin router is never mounted, and
authorize() returns not_found, so nothing admin-scoped is reachable. The
security posture is unchanged; only the blast radius of a config gap is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: Eli Foster <efoster@squareup.com>
2026-08-03 16:50:27 -07:00

3.9 KiB

Read-only deployment moderation dashboard

Buzz can expose a private, deployment-wide read-only dashboard from the existing relay process. It shows open moderation reports and recent product feedback.

Configure BUZZ_ADMIN_HOST and a strong password to activate the dashboard. A private ingress should also limit access to the operator VPN or approved source IPs.

Required configuration:

BUZZ_ADMIN_HOST=admin.example.com
BUZZ_ADMIN_USERNAME=admin
BUZZ_ADMIN_PASSWORD=<random password with at least 16 bytes>
BUZZ_ADMIN_WEB_DIR=/srv/buzz/admin-web

The relay requires HTTP Basic credentials before it serves the dashboard, its assets, any admin API, or an attachment. BUZZ_ADMIN_USERNAME defaults to admin and must not contain a colon; the password has no default, must be 16 to 1024 bytes, and is retained by the relay only as a digest. The configured host and matching browser origin remain defense-in-depth checks. Requests and responses are bounded and uncached. Use HTTPS so the credentials are not exposed in transit, and route admin traffic through the private ingress.

If BUZZ_ADMIN_HOST is set but the credentials are missing or invalid, the relay logs an error, disables the dashboard, and continues serving normally — the admin host then behaves like any other host. The dashboard is an optional surface, so a missing password costs the dashboard rather than the relay. Check startup logs for admin dashboard disabled after any change to these variables.

When the UI runs in a separate pod, proxy /api/admin/v1/* to the relay while preserving the admin Host header. A NetworkPolicy grants the admin pod access to that relay path.

Read routes:

  • GET /api/admin/v1/reports
  • GET /api/admin/v1/reports/:id
  • GET /api/admin/v1/feedback
  • GET /api/admin/v1/feedback/:id

Report reads accept optional communityId, status, reportType, targetKind, after, before, and limit parameters. Limits are capped at 200. Feedback is a bounded newest-first summary from the existing product-feedback repository.

For local review, run just admin-seed before just admin. The seed command also uploads real image and diagnostic fixtures to local MinIO. Feedback search and filters run over the bounded browser result set; the Acted on checkbox is stored in that browser's local storage.

Feedback attachment boundary

Feedback attachment bytes are available only through the feedback-scoped read route:

  • GET /api/admin/v1/feedback/:id/attachments/:sha256

The route uses the same private-ingress, exact admin Host, and same-origin boundary as the JSON API. It is not a generic media endpoint. The relay loads the feedback row, derives its community from server-owned provenance, verifies that host resolution still maps to the row's community_id, and requires the requested SHA-256 to match both the x field and source-community /media/ URL in that row's persisted imeta tag. It then reads the tenant-scoped media sidecar before accessing the shared content-addressed blob. Unknown feedback, unreferenced hashes, malformed paths, and cross-community substitutions all collapse to 404.

Only GET and HEAD are routed. Existing community /media/* authorization is unchanged, including BUZZ_REQUIRE_MEDIA_GET_AUTH; the browser receives no Blossom credential or reusable signed URL. Responses are uncached, nosniff, governed by a restrictive CSP, streamed from object storage, and non-previewable content retains attachment disposition. Successful reads produce a structured trace containing feedback ID, community ID, and attachment hash, but no feedback body or attachment URL.

The deployment credential identifies administrators as a group, not as individual people. Anyone with that credential can read attachments for feedback records they can access. Rotate the password when an administrator loses access. Per-person attribution requires identity-aware authentication at the ingress or application layer.