destroyulator/LANES/LANE5-ship.md
Monster Robot Party a7e409593f LANE5 spec: harden 3GOD (close open mode, plug SSRF) + deploy web toy publicly
LANE4 spot-checked clean (smoke test green, 20 fractured GLBs in game assets, full
web catalog, boss mechanics present). Next blocker between here and a shareable
link is ops: 3GOD writes are still unauthenticated + /api/fetch SSRF, and web/ only
exists on localhost. LANE5 is recon-first and reversible-steps on production boxes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 22:47:22 +10:00

4.3 KiB

LANE 5 — Ship it: harden 3GOD, deploy the web toy to digalot.fyi

Owns: the 3GOD server code/config (wherever it lives — recon first) + the VPS's web serving config for the new route. Does NOT touch game/, web/ app code (except a config constant if the depot URL changes), or meshgod's pipeline. For: an Opus 4.8 session. Pre-req: LANE4 merged (it is — 9dd6824). ⚠️ This lane touches production boxes. Work in small, reversible steps; keep a rollback note for every change; if anything is ambiguous about the owner's infra, STOP and ask rather than guess.

Why now

The web toy reads props live from digalot.fyi/3god, and the whole point of Lane 3 is a public shareable link. Two things stand between here and that link:

  1. 3GOD is wide open (PLAN.md flag, confirmed twice since): GOD3_OPEN=1 means unauthenticated writes (upload/delete) via the tailnet origin, and /api/fetch has an SSRF hole (server.py:173 — accepts file:// and can reach cloud-metadata IPs). Fine as a private tool; not as the backing store of a public game.
  2. The web toy isn't deployed — it only exists in the monorepo and on localhost.

Deliverables

A. Recon (do this first, report before changing anything)

  • Locate the 3GOD deployment: which machine serves the origin dealgod:8788 (tailnet 100.94.195.115), where server.py lives, how it's started (launchd/systemd/ tmux/docker?), and how digalot.fyi (Cloudflare) routes /3god to it.
  • Locate how thriftgod is served on digalot.fyi (same VPS? Caddy/nginx/static dir?) — the web toy should ride the same mechanism.
  • Confirm current behavior as the baseline: CF GET /3god/a/<file> → 200 + CORS *; origin write w/o auth → accepted (open mode); POST /api/fetch with a file:// URL → what does it return today?

B. Harden 3GOD (writes closed, reads stay public)

  1. Close open mode: run with GOD3_OPEN unset. Writes (upload/delete/fetch) must require the existing auth (3g HMAC cookie per server.py::_push_3god contract — set a real GOD3_PW). Store the password where meshgod's push_3god.py can read it (env/openrc-style file next to open.txt, gitignored) and verify a meshgod push still works WITH the password and 401/403s without.
  2. Plug the SSRF: /api/fetch must allow-list schemes (https only) and reject private/metadata hosts (169.254.0.0/16, 10/8, 172.16/12, 192.168/16, localhost, file://, redirects re-checked after resolution). If fetch isn't actually needed for the game workflow, gate it behind auth entirely — simplest correct fix.
  3. Keep reads public: GET /a/<file> and GET /api/list stay unauthenticated with CORS * — the web toy depends on them. Re-run the baseline checks after.
  4. Commit the server changes to 3GOD's own repo with before/after curl transcripts in the commit message. Restart the service the same way it was started.

C. Deploy the web toy

  1. Serve the monorepo's web/ at a stable URL on digalot.fyi (e.g. digalot.fyi/destroy or a subdomain — match however thriftgod is wired; static files, no build step). Simplest robust shape: a checkout/rsync of web/ on the VPS + a deploy note in web/README.md saying exactly how to update it.
  2. Verify from the public internet (not the tailnet): page loads, pointer lock works in normal Chrome; drag-look fallback works in an iframe; props load from the depot (network tab shows /3god/a/*.glb 200s); printer boss chunks; share button works. Test Safari too — it's the weakest target.
  3. Confirm the offline mirror still covers a depot outage (block the depot host in devtools → toy still playable from assets/).

Acceptance

  • Unauthenticated write to the origin → 401/403. Authenticated meshgod push → lands.
  • POST /api/fetch with file:///etc/hosts and http://169.254.169.254/ → rejected.
  • Public reads unchanged (CF GET 200 + CORS *), web toy loads its depot props publicly.
  • The toy is live at its public URL, playable in Chrome + Safari, and the URL is posted in the final report along with the rollback notes.

Out of scope

  • Game content (office level etc.), balance, new mechanics.
  • Cloudflare account/DNS changes beyond routing the existing zone.
  • Rate limiting/CDN caching of the depot (note as a follow-up if reads look heavy).