The Lost Realms
How to stand up a Server Vault, hand it your provider keys, put a login in front of it, and keep it running — written for the person who has to answer for the bill and the secrets.
Start HereWhat a vault is
The Lost Realms runs perfectly well as a single HTML file opened from your own disk. In that arrangement — Direct mode — the provider keys live in the browser, encrypted at rest, and every call to Anthropic or an image service is made by the page itself. Nothing else is involved, and for one person playing on one machine nothing else is needed.
A vault is what you run when that stops being true. It is a small Node server that hosts the game and proxies its calls, so that the keys sit on the server and never reach a browser at all. The client asks for “a GM turn” or “an image from this provider id”; the vault owns the endpoint, injects the key, and answers. The page never names a URL, which is the whole of the SSRF boundary: a proxy that cannot be told where to go cannot be turned into an open relay.
What it gives you
Keys server-side
One Anthropic key and whatever image, audio, video and 3D keys you hold, AES-encrypted on disk and never returned to a client.
An admin page
/admin — keys, providers, usage, media, worlds and settings, in the game's own palette.
A media store
Generated art written once, content-addressed, and referenced by URL — instead of a megabyte of base64 in every save.
Hosted worlds
Publish a realm from the editor, serve it to players, and — if you choose — list it on a registry so strangers can find it.
A sign-in
Auth0 in front of the whole game and the admin page, with separate lists for who may administer and who may play.
A bill you can read
Per-provider usage and cost, tallied server-side, where one browser's ledger could never see the whole of it.
The vault is optional and detected, never configured. The app probes GET /vault/config on load: served by a vault it switches to Vault mode, opened any other way it stays in Direct mode. There is no setting to get wrong on the client.
Five minutes to a vault
Two variables and you are playing. Everything else in this guide is something you add to that.
cd Server
npm install
VAULT_ACCESS_TOKEN="$(openssl rand -hex 24)" \
VAULT_ANTHROPIC_KEY="sk-ant-..." \
npm start
# → open the printed http://localhost:8787/ and play. The key stays on the server.
On localhost the browser is handed the access token automatically, so there is nothing to paste. From anywhere else the token goes only to a signed-in player — see Who may play.
VAULT_MASTER_KEY so the admin page can store keys rather than read them from the environment (Keys). Turn on HTTPS. Then put a login in front of it, in that order — each one is useful without the next.A .env file instead
Everything here can live in Server/.env rather than on the command line; the vault loads it at boot. VAULT_ENV_FILE points somewhere else if you keep it outside the repo, which is the better habit on a real server.
Where everything lives
One directory holds everything a running vault writes. Knowing which file is which is most of knowing what to back up and what must never be committed.
| Path | What it is | Secret? |
|---|---|---|
Server/data/vault-keys.json | The encrypted key store. AES-256-GCM ciphertext; the master key is not in it. | Yes |
Server/data/vault-access.json | Who may administer and who may play, as edited on the admin page. | Personal data |
Server/data/vault-settings.json | Server name and description, provider slot overrides, model allow-lists. | No |
Server/data/vault-providers.json | Custom provider descriptors an admin has authored. | No |
Server/data/vault-usage.jsonServer/data/vault-usage-history.json | Aggregate call counts, tokens and cost. No prompts, no per-user rows. | No |
Server/data/worlds/<uid>/world.json | Published worlds, one pretty-printed file each. Relocatable — see Worlds in a git repo. | No |
Server/data/media/<shard>/<sha>.ext | The content-addressed media store, plus its index. | No |
Server/data/generations/ | The last few raw generation artefacts, for diagnosis. Bounded by VAULT_GENERATIONS_KEEP. | No |
Server/server.key + server.cert | The TLS pair, when you generate one locally. Gitignored. | Yes (the key) |
Server/.env | Whatever you put in it — which is usually every secret you have. | Yes |
Each of those paths is an environment variable away from being somewhere else; Appendix A names them all. The one that moves for a real reason rather than tidiness is VAULT_WORLDS_DIR.
Part IThe environment
A vault is configured entirely by environment variables. They are listed in full in Appendix A; this section is the shape of them — which ones you must set, which ones you set once you have decided something, and which ones exist only because you are debugging.
The two that are not optional
| Variable | Why it is required |
|---|---|
VAULT_ACCESS_TOKEN | Gates the GM proxy. The server refuses to start without one (16 characters or more), because a vault with an open /vault/gm is somebody else's Anthropic bill. Generate a random one; never reuse a provider key. |
VAULT_ANTHROPIC_KEY | The key the vault injects into GM calls — or ANTHROPIC_API_KEY, which it falls back to. Strictly speaking the server starts without it and the game then cannot narrate, which is not a server you would keep. |
Once VAULT_MASTER_KEY is set and you have entered keys on the admin page, the stored key wins and the environment key becomes the bootstrap for a fresh install rather than the live setting.
Deciding where the vault is
Three variables answer three different questions, and conflating them is the most common way a remote vault half-works.
| Variable | The question it answers |
|---|---|
VAULT_HOST / VAULT_PORT | What does the process bind? Loopback by default. Binding anything else requires TLS — the vault will not serve the world in clear text by accident. |
VAULT_PUBLIC_URL | Where does the world reach this vault? The address that goes into a registry listing and the origin Auth0 returns to. It must be an https address a stranger can reach: loopback, LAN and plain http are refused for a listing. |
VAULT_LOGIN_URL | Where should Auth0 send the browser back? Only needed when that is somewhere else — the vault on your own machine, published through a tunnel, whose operator signs in at http://localhost:8787 while the realm is listed at the tunnel address. |
VAULT_LOGIN_URL at a loopback address on a vault that gates play behind Auth0 and it lists perfectly and turns every arriving player away — their browser is sent to a callback on their machine. The vault warns at boot when all three conditions hold. A vault with Auth0 off has open play and no such problem.Keys & the encrypted store
A vault can hold a key two ways. From the environment, which needs nothing else and means editing a file and restarting to change one. Or in the store, which is a file of AES-256-GCM ciphertext that the admin page can write to while the server runs.
The store is off until you set VAULT_MASTER_KEY — 32 bytes, as 64 hex characters or base64. It encrypts the store and is never written beside it: a backup of the data directory alone is not a backup of your keys, and that is deliberate.
VAULT_ACCESS_TOKEN=... VAULT_MASTER_KEY="$(openssl rand -hex 32)" npm start
# → http://localhost:8787/admin — add keys there instead of in the environment.
The providers it knows
Each row on the Keys tab is one provider, and its name is a link to the page where that key is created — the one step the admin page cannot take for you. Two are marked required, which is a claim about the game being playable rather than about the server booting: the desktop launcher gates its Play button on exactly those two.
| Provider | What it buys | |
|---|---|---|
anthropic | The Game Master, and every narration in the game | Required |
nanobanana | Images, gallery variations, portraits, icons, weathering, reactions | Required |
pollinations | Images and portraits — the one provider with a keyless tier | Optional |
openai · fal · higgsfield | Further image providers, each selectable per generation slot | Optional |
elevenlabs | Sound effects and music — two endpoints, one key | Optional |
runware | Video | Optional |
tripo · worldlabs | 3D item models, and 3D room environments | Optional |
vectorizer | Tracing a raster picture into vector glyphs | Optional |
An admin can also author a provider the vault has never heard of, on the Providers tab, as a validated descriptor: an endpoint, an auth style, a request template and a response rule. It is data, not code — the vault runs every descriptor through one trusted executor with the host pinned, so adding a provider can never mean running something new on the server.
HTTPS
The vault speaks HTTPS the moment it has a key and a certificate, and it requires them to bind anything but loopback. For local work, generate a self-signed pair:
./make-cert.sh # localhost + 127.0.0.1 + ::1
./make-cert.sh 192.168.1.20 # ...plus another address you reach the vault by
On Windows make-cert.ps1 is the same script, using openssl when it is on PATH and .NET's own APIs when it is not — that fallback needs PowerShell 7, and says so if you are on 5.1.
Both files are written beside server.js, both are gitignored, and the vault picks them up with no configuration. Set one without the other and it is a fatal misconfiguration, not a quiet fall back to http: a server that has been told to be encrypted and silently is not is worse than one that refuses to start.
TLR_ALLOW_INSECURE_CERT=1 accepts it for the vault's own host during development and must never ship. The cure for both is a local root CA: one root installed into the machine's trust store, and a certificate signed by it. The full recipe is on the admin page under Settings › Server, because that is the screen that raised the question — and its openssl half is executed by a test rather than proofread.A certificate must carry a subjectAltName; browsers stopped honouring the Common Name years ago. For anything genuinely public, terminate TLS at a reverse proxy with a real certificate and let the vault stay on loopback behind it.
Sign-in with Auth0
Set four variables together and the whole game requires a sign-in, not just the admin page. An unauthenticated request to any app page is redirected to Auth0's Universal Login; only a successful login gets the game. The same session gates /admin, which additionally requires an email on the admin list.
VAULT_ACCESS_TOKEN=... VAULT_MASTER_KEY="$(openssl rand -hex 32)" \
AUTH0_ISSUER_BASE_URL=https://auth.example.com \
AUTH0_CLIENT_ID=... \
AUTH0_SESSION_SECRET="$(openssl rand -hex 32)" \
VAULT_PUBLIC_URL=https://vault.example.com \
VAULT_ADMIN_EMAILS=you@example.com \
npm start
A Native application, not a Regular Web Application
The vault is a public client signing in with Authorization Code + PKCE. Create the Auth0 application as Native, with Token Endpoint Authentication Method None. A Regular Web Application is confidential, and Auth0 would issue it a client secret this code has nowhere to put — which is also what lets the vault ship inside the desktop app, where a secret would be sitting in every copy.
AUTH0_CLIENT_SECRET is not used and is safe to delete. AUTH0_SESSION_SECRET is required, is a different thing entirely — it signs this vault's own session cookie — and is shared with nobody. Generate a fresh random value; do not paste an Auth0 secret into it.The two URLs to register
| Auth0 setting | Value |
|---|---|
| Allowed Callback URLs | <VAULT_PUBLIC_URL>/admin/callback |
| Allowed Logout URLs | <VAULT_PUBLIC_URL> — the base URL itself |
http://localhost:8787, never …/admin and never …/admin/logout. Auth0 matches these entries exactly: it allows a wildcard only in the subdomain position, never in a path, and it does no prefix matching. Register …/admin and every sign-out lands on an Auth0 error page while sign-in keeps working perfectly, which is what makes it confusing.The custom domain matters
Where the tenant has a custom domain, AUTH0_ISSUER_BASE_URL must be that domain (https://auth.example.com), not the canonical https://TENANT.REGION.auth0.com. Auth0 keeps a separate login session per domain: a vault on one while your website signs people in through the other means two cookie jars, and somebody already signed in to the site gets the full login screen again at the vault. It reads as broken SSO and it is really just two domains. Every surface on the tenant must name the same one.
https:// form. The vault corrects VAULT_PUBLIC_URL for you — holding a key/cert pair, it raises an http:// public URL to https:// before handing it to Auth0, because the redirect_uri must carry the scheme the pages are served over or the callback is refused as a mismatch. The startup banner says when it did that. Change the variable anyway: the vault fixes the login, not your notes.Part IIThe admin page
/admin is the whole of the running vault's controls, in the game's own palette. With Auth0 configured it is gated by the admin allow-list. With Auth0 not configured it is reachable only from loopback — so a local install needs no identity provider, and a remote one is never accidentally exposed.
| Tab | What it is for |
|---|---|
| Keys | Set, replace and remove provider keys. Leads, because a vault you have just installed does nothing at all until the Anthropic key is in. |
| Providers | The built-in descriptors, which slots each may fill, and any custom provider you author. Restricting a provider to fewer slots is done here. |
| Usage | Calls, tokens, credits and cost per provider, with history. Aggregate only. |
| Media | What the media store holds, what each file was generated from, and a sweep for files no hosted world references. |
| Worlds | The realms this vault serves: download one, remove one, or tick Public to list it on a registry. |
| Logs | The last sixty outbound provider calls, redacted, in memory only. |
| Settings | Server name and description, the public listing, the model allow-lists, the access lists, and a read-only environment report. |
The environment report
The fastest way to answer where are my worlds or is TLS on without a shell. Every row is the resolved value the process is using, plus whether it came from the environment or from a default — so a blank means “using the default” rather than “unset and broken”.
Who may play
Three lists and a switch, and the one that surprises people is the second.
| Setting | Meaning |
|---|---|
VAULT_OWNER_EMAIL | The owner admin. Always an admin, and cannot be removed from the admin page by anyone — only by changing the variable. It is what guarantees a deployment can never be locked out of its own administration. Defaults to the first admin email. |
VAULT_ADMIN_EMAILS | Who may reach /admin. Seeds the list at boot; edits made on Settings › Access are stored and win on later starts. |
VAULT_PLAYER_EMAILS | Who may play, in addition to the admins and the owner. |
VAULT_ANYONE_CAN_JOIN | Ignore the player list entirely: any authenticated user may play. |
VAULT_ANYONE_CAN_JOIN, which also lets a curated list be kept on file and set aside rather than deleted. Only affirmative spellings count — =0 is off.A signed-in player is handed the access token by /vault/config exactly as a loopback client is, so the bearer-token proxy is unchanged and stays CSRF-immune. Nothing on the client changes when you turn a login on.
Worlds & the registry
A published world is one pretty-printed world.json in a directory named by its uid, written by rename so a reader always sees a whole file. The vault serves it, the Worlds tab lists it, and the directory — not an index beside it — is the record.
Making one public
Ticking Public offers the world to a central registry, so that players browsing it can find you. It sends the public URL, the name and the description, and nothing else. The box stays disabled, with reasons, until the listing would actually be accepted — which mostly means VAULT_PUBLIC_URL being a real https address.
Taking one back
Unticking Public withdraws the listing — whether the registry had approved it or still had it pending review. It asks first, because a withdrawal destroys the listing and, on a registry that queues arrivals, the place in the queue with it: a realm re-published afterwards arrives pending again, behind everything that was already waiting. When you confirm, the vault records the decision and then calls the registry, and a second dialog says what actually happened — whether the realm is off the registry, or still on it and why.
That second dialog is the point. The vault writes its own flag before it calls the registry, so a call that is refused leaves the box unticked here and the listing standing there — and the box, now unticked, is no longer a control that can ask again. So a refused withdrawal grows a Withdraw & try again button in that cell, the same one a realm the registry has denied gets, and pressing it makes the same request. A world carrying a key is the common case: the registry will not unlist it on this vault's word, because the private half of the key is the author's, so the withdrawal stops at a challenge and the row offers Choose key file… to finish it. Until somebody does, the realm stays listed.
| Variable | Effect |
|---|---|
REGISTRY_URL | Unset, this is the project's registry and ticking Public publishes there for real. Point it at another registry to publish there, or set it to nothing (or none) to publish nowhere — the tick then records the decision and validates the descriptor. The path matters as much as the host: the base is joined to /api/worlds/… as given. |
REGISTRY_TOKEN | The publisher token, where that registry asks for one. A registry with no tokens configured accepts anonymous writes, which is a LAN arrangement rather than a public one. |
REGISTRY_ALLOW_LOCAL | Development only. Lets a vault publish itself at a localhost or LAN address over http, so a vault and a registry on one machine can talk. Set it on both processes — each validates independently. It does not relax https for a public address, and does not skip the origin check. Announced at boot. |
The heartbeat, and the one route nobody gates
For each world marked Public the vault sends a heartbeat — shortly after boot and once a day — so listings do not lapse; a registry drops one a week after anything last confirmed it. The heartbeat carries nothing at all: no descriptor, no key, no signature. It cannot change a listing, only confirm it, which is what lets a keyed world stay listed without its author present to sign.
GET /vault/registry-proof is the single route a registry calls on your vault, and it is deliberately ungated: the asker is a registry you have no account with, arriving from an address nobody can predict, so a login gate here would mean a vault behind Auth0 could never be listed. It answers with the nonce echoed back only for a world hosted here and marked Public. Everything else is a 404 indistinguishable from “no such world”, so it cannot be used to enumerate what you hold privately. That is what stops a registry listing pointing at a server which never agreed to serve the world.
The media store
Generated art used to travel inside the save as a base64 data URI. The vault already holds those bytes — it is the thing that called the provider — so it keeps them on disk and hands back a URL instead. Files are named by the SHA-256 of their content, sharded by the first byte, so the same icon reachable from a catalogue, a pack and a room floor is one file rather than three, and regenerating an identical image is a no-op.
Each stored file also carries a provenance record: the provider, the model and a truncated prompt. That is the one place a prompt is written to disk rather than held in memory; VAULT_MEDIA_PROVENANCE=0 turns the capture off and leaves the store working.
/vault/media/…. With it off, the same world carries its art inline — which still works, and grows by the size of every image on every publish. For a world that is already a file on disk, node Tools/install-world-media.js <world.json> moves the art out first.Admin › Media lists what is held, shows what made each file, removes any of them, and can sweep every file no hosted world references. Removal refuses by default when a published world still points at the file, and names the worlds. Note that a vault can only see worlds published to it: art that a player's own browser save refers to counts as unreferenced.
Usage, cost & the call log
Every proxied call is tallied server-side: requests, tokens where the provider reports them, credits where it bills that way, and a cost in dollars. It is aggregate only — no prompts, no per-user rows — and it is the only place the whole bill is visible, since a browser's own ledger sees one browser.
Beside it, the call log holds the last sixty outbound provider calls with the resolved URL and the prompt that was sent, in memory, redacted of keys, and never written to disk. VAULT_CALL_LOG=0 turns it off; VAULT_CALL_LOG_QUIET=1 keeps it but stops it printing to the console.
Part IIIBackups & secrets
Three things are worth backing up, and they want three different treatments.
| What | Treatment |
|---|---|
| The master key | Keep it wherever you keep passwords, and not in the data directory. It is not in the key store, so a copy of that file is useless without it — which is the point, and also means losing the key loses every stored provider key. They can be re-entered; nothing else can recover them. |
| The worlds directory | The thing that is actually irreplaceable. A git checkout is the good answer. |
| The data directory | Access lists, settings and the media store. Worth having; none of it is unrecoverable if you still have the worlds and the keys. |
vault-keys.json (ciphertext, but ciphertext of your money), vault-access.json (other people's email addresses), server.key, and .env. They are gitignored in this repository; if you move the data directory somewhere else, they are only as ignored as you make them.The claims this guide makes about what is stored, logged and sent are the same ones PRIVACY.md and SECURITY.md make, and those two documents are part of any change that moves data. If you are deciding whether to run a vault for other people, read them; they are short.
Worlds in a git repo
A world is a JSON file in a directory named by its uid — a git-shaped layout already. VAULT_WORLDS_DIR is what lets you put it in an actual repository: version history for your realms, a diff for every publish, and a way to move a world between machines that is not an emailed 400 MB file.
git clone git@example.com:you/realms.git /srv/realms
VAULT_WORLDS_DIR=/srv/realms/worlds VAULT_ACCESS_TOKEN=... npm start
Leave it unset and nothing changes — the default is exactly where worlds have always lived, so an existing install keeps every one of them with no migration. Setting the variable is the only thing that relocates anything, and moving is then a mv:
mv Server/data/worlds/* /srv/realms/worlds/
cd /srv/realms && git add worlds && git commit -m 'Import the vault worlds'
Because the directory is the record, both things git does to a directory work. A world pulled in is listed without any publish, its record rebuilt from the file — it reports no publisher, because a file cannot say who published it, and shows as adopted. A world edited or reverted on disk is re-read or dropped on the next request. Unchanged worlds cost one stat each.
data/.Upgrading
Pull, npm install if the dependencies moved, restart. Two things make that less alarming than it sounds.
The vault notices when its own source on disk has changed since the process booted and tells every connected client, which shows a “restart needed” banner. So the failure mode where you pull on the server, forget to restart, and spend an afternoon wondering why a fix did nothing is one the app announces rather than one you have to remember.
Nothing in the data directory is version-stamped or migrated on start: the key store, the access lists, the settings and the worlds are read as they are. An older world file is read by a newer vault, and the app's own backfill merges new built-in content into old saves on load rather than refusing them.
When it will not work
The failures a vault actually produces, and what each one means. Nearly all of them are one of three things: a variable naming somewhere you are not, a URL registered somewhere that does not match, or a key that is present in a place nothing reads.
| What you see | What it is |
|---|---|
| The server refuses to start, naming the access token | No VAULT_ACCESS_TOKEN, or one shorter than 16 characters. It is deliberate: an ungated GM proxy is an open relay on your Anthropic bill. |
| It refuses to start, naming TLS | Either you bound a non-loopback host without a key and certificate, or you set one of VAULT_TLS_KEY/VAULT_TLS_CERT without the other. Neither falls back to plain http, on purpose. |
| “The Claude/GM key is required to play” at boot | No Anthropic key is resolvable — neither stored nor in the environment. If you entered one on the admin page, check that VAULT_MASTER_KEY is still the same value it was when you entered it: a changed master key leaves the store unreadable and the vault reports the key as stored-but-unreadable rather than pretending it is gone. |
| Sign-in works, sign-out lands on an Auth0 error | Allowed Logout URLs does not contain your public URL exactly. It is the base URL with no path — see the warning; /admin or /admin/logout there is the usual cause, and it never breaks sign-in, which is what makes it puzzling. |
| Sign-in bounces back to the login screen, or the callback is refused as a mismatch | The callback URL registered in Auth0 does not match <VAULT_PUBLIC_URL>/admin/callback — most often over the scheme, after TLS was turned on and only the vault knew. |
| Somebody already signed in to the website gets a full login screen at the vault | Two Auth0 domains, so two cookie jars. Point AUTH0_ISSUER_BASE_URL at the tenant's custom domain, the same one every other surface uses. |
| Players are sent to a callback on their own machine | VAULT_LOGIN_URL is a loopback address on a vault that gates play. The boot warning says so; the fix is to leave it unset unless you are running the tunnel arrangement it exists for. |
| It starts up saying Auth0 is not configured | The gate needs four values and comes on only when it has all of them: AUTH0_ISSUER_BASE_URL, AUTH0_CLIENT_ID, AUTH0_SESSION_SECRET and VAULT_PUBLIC_URL (or VAULT_LOGIN_URL). The startup banner and Settings › Server › Environment both name the ones that are not set. The usual culprit is the fourth, because it is the only one not called AUTH0_*. AUTH0_CLIENT_SECRET is not one of them and setting it will not help — the login is PKCE against a public client and sends no secret. |
| The Public tick is disabled | The listing would be refused, and the page says why — nearly always VAULT_PUBLIC_URL being loopback, LAN or plain http. |
| You unticked Public and the realm is still on the registry | The withdrawal was refused and the dialog said so. Nearly always a keyed world: the registry needs a signature from the key file before it will unlist one, and until it gets it the listing stands. Press Withdraw & try again on that row, or Choose key file… to sign. A registry that is simply down, or a REGISTRY_TOKEN it no longer recognises, reads the same way — the dialog names which. |
| The desktop app will not connect to your dev vault | It refuses an untrusted certificate by design. TLR_ALLOW_INSECURE_CERT=1 for development only, or trust a local root CA and stop needing it. |
| A generation fails and you want to know why | The server console names the provider, the status and the upstream's own message. VAULT_DEBUG=1 adds a per-generation line saying whether a key was found and how the request was authorised — redacted to the last four characters. |
Appendix AEvery variable
Everything the vault reads from its environment, grouped by what it is for. A blank default means unset and unused rather than unset and broken, unless the row says otherwise.
Required, and the keys
| Variable | Default | Purpose |
|---|---|---|
VAULT_ACCESS_TOKEN | — required | Gates POST /vault/gm and the other proxy routes. 16 characters or more, or the server will not start. |
VAULT_ANTHROPIC_KEY | — | The Anthropic key the vault injects into GM calls. |
ANTHROPIC_API_KEY | — | Fallback for the above, so a machine that already has the conventional variable needs nothing new. |
VAULT_MASTER_KEY | — | 32 bytes (64 hex or base64) encrypting the key store. Absent, the store is disabled and keys come from the environment only. |
VAULT_KEYS_FILE | Server/data/vault-keys.json | Where that encrypted store lives. |
Where it listens, and where it is
| Variable | Default | Purpose |
|---|---|---|
VAULT_HOST | 127.0.0.1 | Bind address. Anything but loopback requires TLS. |
VAULT_PORT | 8787 | Port. |
VAULT_TLS_KEY / VAULT_TLS_CERT | auto: server.key + server.cert | PEM paths. Both or neither; one alone is fatal. |
VAULT_TLS_AUTO | 1 | Set to 0 to ignore a key/cert pair sitting beside server.js and stay on http. |
VAULT_PUBLIC_URL | — | Where the world reaches this vault: registry listings, the origin-proof answer, and the Auth0 redirect origin. |
VAULT_LOGIN_URL | VAULT_PUBLIC_URL | Where Auth0 returns the browser, when that differs — the tunnel case. |
VAULT_STATIC_DIR | repo root | The directory the app is served from. |
VAULT_ENV_FILE | Server/.env | Where the .env loader reads from at boot. |
Sign-in and access
| Variable | Default | Purpose |
|---|---|---|
AUTH0_ISSUER_BASE_URL | — | The tenant, as its custom domain where it has one. |
AUTH0_CLIENT_ID | — | The Native (public) application's client id. |
AUTH0_SESSION_SECRET | — | Signs this vault's own session cookie. Not an Auth0 secret; generate a random value. |
AUTH0_CLIENT_SECRET | — | No longer used — the vault signs in with PKCE. Safe to remove. |
VAULT_ADMIN_EMAILS | — | Comma-separated admin allow-list. Seeds the stored list, which then wins. |
ADMIN_EMAILS | — | Fallback spelling of the above. |
VAULT_OWNER_EMAIL | first admin email | The owner: always an admin, removable only here. |
VAULT_PLAYER_EMAILS | — | Who may play besides the admins. Empty is not “everyone”. |
VAULT_ANYONE_CAN_JOIN | off | Open play to any authenticated user. Affirmative spellings only. |
VAULT_MODE | — (shared token) | credits makes every /vault/* call an account’s: a player Auth0 access token is required instead of the shared vault token, which then opens nothing. Needs AUTH0_ISSUER_BASE_URL; the vault refuses to start without it. In development, for the credits system — leave unset on an ordinary vault. |
VAULT_PLAYER_AUDIENCE | https://thelostrealms.ai/player | The API audience a player access token must be issued for, in credits mode. A token for any other API of the tenant is refused. |
VAULT_PORTAL_URL | http://127.0.0.1:8793 | The credits portal this vault reserves and settles every GM call against, in credits mode. A loopback neighbour by default; unreachable means calls are refused, never made uncharged. |
VAULT_PORTAL_SECRET | — (required in credits mode) | The service secret the portal holds (its PORTAL_SERVICE_SECRET), 32+ characters. The vault refuses to start in credits mode without it. |
VAULT_ACCOUNT_URL | — (credits mode, optional) | Where a player who runs out of credits is sent to buy more — the Account Page. Without it the game says the player is out of credits and offers no link. |
VAULT_RATE_LIMIT | 120 | Requests a minute one caller may make to /vault/, counted per signed-in session or token (not per address, so players behind one proxy do not share a budget). Media reads are not counted. 0 turns the limit off. A caller over it gets a 429 and is told to wait a moment. |
VAULT_FIRST_USER_IS_OWNER | — | Trust on first use, for the desktop shell. Never on a server. |
VAULT_ACCESS_FILE | data/vault-access.json | Where the edited allow-lists are stored. |
Worlds, the registry and what it stores
| Variable | Default | Purpose |
|---|---|---|
VAULT_WORLDS_DIR | worlds/ beside the settings file | Where published worlds live. The one path worth relocating. |
VAULT_SETTINGS_FILE | Server/data/vault-settings.json | Server name and description, slot overrides, model allow-lists. |
VAULT_PROVIDERS_FILE | Server/data/vault-providers.json | Admin-authored custom provider descriptors. |
VAULT_USAGE_FILE / VAULT_USAGE_HISTORY_FILE | Server/data/vault-usage*.json | The aggregate usage tallies and their history. |
VAULT_MEDIA_PROVENANCE | on | 0 stops recording what generated each stored file; the store keeps working. |
VAULT_SERVER_NAME / VAULT_SERVER_DESCRIPTION | — | How the server introduces itself. These only seed the fields; saved edits win. |
REGISTRY_URL | the project's registry | Where Public listings go. Empty or none publishes nowhere. |
REGISTRY_TOKEN | — | Publisher token, where the registry asks for one. |
REGISTRY_ALLOW_LOCAL | off | Development only: allow a localhost/LAN listing. Set on both processes. |
Diagnostics
| Variable | Default | Purpose |
|---|---|---|
VAULT_DEBUG | off | Per-generation diagnostics on the console: whether a key was found, how the request was authorised, redacted to the last four. |
VAULT_CALL_LOG | on | 0 switches off the in-memory log of the last sixty provider calls. |
VAULT_CALL_LOG_QUIET | off | 1 keeps the log but stops it printing to the console. |
VAULT_GENERATIONS_DIR | generations/ beside the settings file | Where the last few raw generation artefacts are kept for diagnosis. |
VAULT_GENERATIONS_KEEP | 10 | How many to keep. |
Tests/test_vault_guide.js reads the variable names out of Server/ and fails if this appendix has fallen behind them. A guide that quietly stops describing the server is worse than no guide, and a list of forty names is exactly the thing nobody notices going stale.Appendix BRoutes
What a vault answers, and who may ask. You do not call these by hand — the app does — but knowing they exist is how you read a log line or a reverse-proxy rule.
| Route | Gate | What it does |
|---|---|---|
GET /vault/config | open; the token only to a loopback or signed-in client | What makes Vault mode detectable: the proxy URLs, the provider catalog, the model ceilings and whether a GM key is resolvable. |
POST /vault/gm | bearer token | The GM proxy. One allow-listed upstream; the client never names a URL. |
POST /vault/generate | bearer token | Image, sound, music and glyph generation by provider id, run from a host-pinned descriptor. |
POST /vault/video · /vault/model3d · /vault/worldgen · /vault/text | bearer token | The four flows that are code rather than descriptors — each is a submit-then-poll or a second content type. |
POST /vault/evaluate | bearer token | Runs a world evaluation server-side. |
GET /vault/media/<shard>/<sha>.ext | open (same-origin content) | Serves a stored file. Names are computed from content, so there is no input to sanitise. |
GET /vault/registry-proof | ungated, deliberately | Echoes a registry's nonce for a world hosted here and marked Public; a 404 for anything else. |
GET /admin and /admin/api/… | admin allow-list, or loopback with Auth0 off | The admin page and everything on it. Every request is re-checked server-side. |
/admin/login · /admin/callback · /admin/logout | — | The Auth0 round trip. The callback is the URL you register; the logout redirect returns to your public URL. |
Server/, Tests/, Designs/ and the rest — plus path traversal and dotfiles. Anything new at the top level is therefore served the moment it exists, which is why adding a top-level directory to this project is a decision with a test behind it rather than a default.Appendix CAbout this guide
This is the operator's companion to the Dungeon Master's Guide and the in-game Field Guide: same palette, same chrome, a different job. Where they describe authoring and playing a world, this one describes the machine that serves it.
Its source of record is the repository itself — Server/README.md for the configuration, SECURITY.md and PRIVACY.md for the boundaries and what is stored, and Designs/server-vault.html, Designs/vault-media-store.html and Designs/realm-registry.html for why each is shaped the way it is. Where this guide and the server disagree, the server is right, and the disagreement is worth reporting.
Tests/test_vault_guide.js, which reads every environment variable name out of Server/ and every provider from the managed-key roster, and fails when this guide has fallen behind either. The Auth0 rows are pinned the same way, against the routes Server/identity.js actually installs — because the logout row in particular is a value that looks right in three plausible spellings and works in exactly one.The Lost Realms · Vault Administrator's Guide · styled after the Field Guide. Copyright © 2026 Brave You Worlds, LLC. All rights reserved. This guide is Game Content, not software: licensed under LICENSE-CONTENT, with no Change Date — it does not become open source on any future date, the way the engine does. Free to read, print and use in personal or non-commercial play, permanently; commercial use needs a separate licence — licensing@braveyouworlds.com.