From aadc13f6f4c7284f872630ad232871e3905917b8 Mon Sep 17 00:00:00 2001 From: wtclaude Date: Fri, 23 Oct 2026 18:15:10 -0500 Subject: [PATCH] =?UTF-8?q?docs(website):=20DM14=20=E2=80=94=20open=20the?= =?UTF-8?q?=20plugin=20config=20editor,=20hide=20the=20bridge's?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Visitors may read and change every plugin config except the bridge's: the editor's save reloads through the bridge (with rollback, D178/D179), not the console, and the wipe restores oxide/config. The RunicGateway plugin's files are refused for reading as well as writing and left out of the file list, because they hold credentials for talking to the sidecar directly. Because the editor masks secret fields only in the form today, the demo also redacts flagged values server-side, makes them read-only, refuses raw-JSON saves, and leaves the sidecar URL out of the Servers admin view. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- website/DEMO_MODE.md | 56 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/website/DEMO_MODE.md b/website/DEMO_MODE.md index c18c136..b851b0a 100644 --- a/website/DEMO_MODE.md +++ b/website/DEMO_MODE.md @@ -1,6 +1,6 @@ # Demo mode — `demo.runicgateway.com` -**Status:** design, **fully decided** 2026-10-23 (DM1-DM13, §9). Ready to build per §10. Nothing is +**Status:** design, **fully decided** 2026-10-23 (DM1-DM14, §9). Ready to build per §10. Nothing is built yet. **Issue:** `RunicGateway/website#222`. **Why now:** Google Play (`../android/PLAN.md` item 9, M8) needs a public HTTPS site with working sign-ins for its reviewers and for the 12 testers who @@ -49,8 +49,8 @@ The web client shows the message as a toast wherever an action fails with that c app maps the code to a new error kind with the same wording; today it would show the generic "Something went wrong", which is misleading for a reviewer. -Reads are never locked. Every page can be opened and every form filled in; only the save is -refused. That keeps the demo useful as a tour of the admin panel. +Reads are never locked, with one exception: the bridge's config (§3.5). Every page can be opened +and every form filled in; only the save is refused. That keeps the demo useful as a tour of the admin panel. **The server is the lock, never the UI (DM10).** Every lock is enforced at the route, so a request sent with `curl`, an old app build, or a hand-edited page is refused exactly like a click. The UI may @@ -104,7 +104,7 @@ wipe puts them back (§7). | Area | Routes | Why | | --- | --- | --- | | The link to the rig | `PUT`/`DELETE /admin/rust/servers/:id` | Repoint or delete the sidecar link; the site goes dark | -| Plugin config files | `POST /admin/rust/config/:serverId/file` | Writes a plugin's config file on the rig's disk | +| The bridge's config, **read or written** | `GET`/`POST /admin/rust/config/:serverId/file`, `GET …/writes…` for the `RunicGateway` plugin's files, which are also left out of `GET …/files` | It holds the bridge's link to the sidecar and its credentials. Read, it would let someone talk to the sidecar directly over HTTP (DM14, §3.5) | **Settings stay open (DM2).** Theme, navigation and the other `PUT /admin/settings` values can be changed and come back at the wipe. Brand images are uploads, so they fall under the upload lock. @@ -166,6 +166,41 @@ Commands that only change the world or the players (`server.writecfg`, `entity.* `weather.*`, `say`, `kick`, `ban`, `oxide.grant`) stay open. The baseline restore puts all of them back (§7.2). +### 3.5 The plugin config editor (DM14) + +Every plugin's config **except the bridge's** can be read and changed on the demo. A visitor can tune +RunicNPC, Kits, Economics and the rest and watch the change take effect. Three things make that safe: + +- **The save reloads through the bridge, not the console.** The bridge backs up the file, writes + it, reloads the plugin that owns it, and puts the old file back if that plugin fails to load (D178, + D179). The §3.4 deny list never sees this reload, and a config that breaks its plugin undoes + itself at once. +- **The wipe restores `oxide/config/`** from the baseline (§7.2), so a change lasts two hours at + most. +- **The bridge's own config is out of reach.** The editor's routes refuse every file the + `RunicGateway` plugin owns, for reading as well as writing, and the file list leaves those files + out. This is the one place where §3.1's "reads are never locked" gives way. The file holds the + bridge's link to the sidecar and its credentials, and anyone holding them could talk to the + sidecar directly over HTTP, outside every lock in this document. + +**Secrets in other plugins' configs.** A plugin config can hold a webhook URL or an API key. The +editor already flags those fields by key name (`SECRET_WORDS` in `configEdit.js`), but today it +returns the full text and masks them only **in the form**, behind a reveal button. Under DM10 that +mask is not a lock. On the demo, the server therefore: + +- **redacts** every flagged value in `GET …/file` and in the write history before it leaves the + module, replacing it with a placeholder; +- makes flagged fields **read-only**: a form save that edits one is refused; +- refuses **raw-JSON saves** (`text` instead of `edits`), because the raw document a visitor sends + back would hold the placeholders and overwrite the real values. Form saves splice only the fields + that changed, so untouched secrets stay as they are on disk. + +**The link on the Servers screen.** The module's admin view of a server never returns the sidecar +token; it returns `hasToken`. On the demo it also leaves out the sidecar's **URL**, so the screen +gives away nothing that helps someone reach the sidecar. Editing the link stays locked (§3.3). + +All of this is enforced in the module's server code behind `ctx.demoMode` (DM10), with tests. + ## 4. Static logins | Account | Role | Notes | @@ -307,7 +342,7 @@ down. | Piece | Repo | | --- | --- | | `DEMO_MODE` lock, banner field, `seed:demo` script | `website` (core) | -| The Rust module's `demoLocks`, the console-command deny list (§3.4), and `protocol` in its install manifest (§7.5) | `Module-Rust` | +| The Rust module's `demoLocks`, the console-command deny list (§3.4), the config-editor rules (§3.5), and `protocol` in its install manifest (§7.5) | `Module-Rust` | | `DEMO_LOCKED` error kind and the banner in the app | `Android-app` | | `deploy/demo/` (compose file, `.env.example`, seed fixture) and `.gitea/workflows/demo-wipe.yml` + `demo-baseline.yml` | `website` (DM8) | | `.env` (JWT secret, encryption key, the rig's sidecar token) | On the `demo-site` host only, never in a repo | @@ -333,6 +368,7 @@ down. | DM11 | (F1) Console-command steps on the demo | A **deny list** of the commands that would break the rig (`quit`, `oxide.unload RunicGateway` and the like), not an allowlist; everything else runs (§3.4) | | DM12 | (F2) What "latest" means across the Rust link | The latest **Rust bundle** for the rig's plugins and sidecar, the newest module release on the same protocol, and the website's `main` image; the wipe updates the rig when the bundle changes (§7.5) | | DM13 | (F3) DM2 read correctly? | Yes: only the settings stay open; every other Tier A row stays locked | +| DM14 | (after DM13) May visitors change plugin configs? | Yes, every plugin except the bridge. The bridge's config can be **neither read nor written**, because it holds credentials that would let someone talk to the sidecar directly (§3.5) | DNS and HTTPS for `demo.runicgateway.com` come from an external reverse proxy, which the org lead is setting up. The app sits behind it on plain HTTP; core already trusts one proxy hop for @@ -354,9 +390,9 @@ through that proxy. `seed:demo`, `noindex`, the web banner and toast, the `MODULE_API` minor bump, and the OpenAPI note that a route may answer `DEMO_LOCKED`. Tests for every lock. One PR, plus `MODULE_API.md` and this doc. -2. **Module** (`Module-Rust`): `demoLocks` in `module.json`, the console-command deny list behind - `ctx.demoMode` (§3.4), and `protocol` in the release's install manifest (§7.5), each with - tests. One PR. +2. **Module** (`Module-Rust`): `demoLocks` in `module.json`; behind `ctx.demoMode`, the console-command + deny list (§3.4) and the config-editor rules (§3.5); and `protocol` in the release's install + manifest (§7.5). Each with tests. One PR. 3. **Kit** (`Integration-kit`): a paragraph on `demoLocks` and `ctx.demoMode`, and the `core-ref.json` re-pin. One PR. 4. **App** (`Android-app`): the `DEMO_LOCKED` error kind and the banner on Home. One PR. @@ -364,6 +400,8 @@ through that proxy. `demo-baseline.yml`; the first deploy on `demo-site`; setting up the default set and capturing the baseline. Then a walk of every static login, every Tier A lock and the upload lock, in the browser and the app, plus one full wipe cycle that restores a changed permission, NPC and - event, and a denied console command refused at save and at run. One PR. + event; a denied console command refused at save and at run; and a plugin config changed and + reloaded through the editor, with the bridge's config refused for reading and another plugin's + secret field redacted. One PR. 6. **Play**: the sign-in details in the Play Console point at `play-review`; runicgateway.com `/beta` publishes the logins.