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>
This commit is contained in:
parent
9dd6824c14
commit
a7e409593f
71
LANES/LANE5-ship.md
Normal file
71
LANES/LANE5-ship.md
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
# 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).
|
||||||
Loading…
Reference in New Issue
Block a user