fix(server): require gateway secrets

This commit is contained in:
Guillaume ARM 2026-06-17 01:56:03 +02:00
parent 84a9aeea25
commit 96efe21b50
3 changed files with 37 additions and 11 deletions

View File

@ -80,7 +80,10 @@ MCP endpoint (OpenCode connects here): http://127.0.0.1:4445
The MCP endpoint stays bound to `127.0.0.1` on purpose: `exec-lua` / `write-file` are The MCP endpoint stays bound to `127.0.0.1` on purpose: `exec-lua` / `write-file` are
as powerful as the linked computer, so only local processes (your OpenCode) reach them. as powerful as the linked computer, so only local processes (your OpenCode) reach them.
Set `TRAPOS_SERVER_PASSWORD` in `.env` to require a shared secret on the gateway connection. `TRAPOS_SERVER_PASSWORD` (and `OPENCODE_SERVER_PASSWORD`) must be set in `.env`: the
gateway connection requires this shared secret, and the server refuses to start if either
is empty (run `just install` to generate them). An empty password would expose the gateway
unauthenticated, so it is never allowed.
## 5. Link The Computer To The Gateway ## 5. Link The Computer To The Gateway
@ -88,7 +91,7 @@ On the ComputerCraft computer, point it at the gateway and reboot:
```sh ```sh
set cloud.url ws://<public-host>:4444 set cloud.url ws://<public-host>:4444
set cloud.password secret # only if TRAPOS_SERVER_PASSWORD is set on the host set cloud.password secret # must match TRAPOS_SERVER_PASSWORD on the host
reboot reboot
``` ```

View File

@ -1,17 +1,21 @@
# Serve the trapos-server gateway (port 4444) for remote/in-game attachment using # Serve the trapos-server gateway (port 4444) for remote/in-game attachment using
# repo-local .env secrets. Mirrors `opencode-serve`, but auth is optional: an unset # repo-local .env secrets. Mirrors `opencode-serve`. Both TRAPOS_SERVER_PASSWORD and
# TRAPOS_SERVER_PASSWORD means the gateway runs with auth disabled (warn, do not fail). # OPENCODE_SERVER_PASSWORD are required: an empty one would run the gateway
# unauthenticated, so a missing secret is a fatal error (see also the guard in index.ts).
[positional-arguments] [positional-arguments]
serve *args: serve *args:
#!/usr/bin/env bash #!/usr/bin/env bash
set -euo pipefail set -euo pipefail
repo='{{justfile_directory()}}' repo='{{justfile_directory()}}'
if [ -f "$repo/.env" ]; then set -a; . "$repo/.env"; set +a; fi if [ -f "$repo/.env" ]; then set -a; . "$repo/.env"; set +a; fi
if [ -z "${TRAPOS_SERVER_PASSWORD:-}" ]; then for var in TRAPOS_SERVER_PASSWORD OPENCODE_SERVER_PASSWORD; do
printf '%s\n' 'serve: TRAPOS_SERVER_PASSWORD unset in .env — gateway auth disabled' >&2 if [ -z "${!var:-}" ]; then
fi printf '%s\n' "serve: $var is empty or unset in .env — refusing to start (run \`just install\` to generate secrets)" >&2
export TRAPOS_SERVER_PASSWORD="${TRAPOS_SERVER_PASSWORD:-}" exit 1
export OPENCODE_SERVER_PASSWORD="${OPENCODE_SERVER_PASSWORD:-}" fi
done
export TRAPOS_SERVER_PASSWORD
export OPENCODE_SERVER_PASSWORD
export OPENCODE_URL="${OPENCODE_URL:-http://127.0.0.1:4242}" export OPENCODE_URL="${OPENCODE_URL:-http://127.0.0.1:4242}"
export TRAPOS_SERVER_HOST="${TRAPOS_SERVER_HOST:-0.0.0.0}" export TRAPOS_SERVER_HOST="${TRAPOS_SERVER_HOST:-0.0.0.0}"
export TRAPOS_SERVER_PORT="${TRAPOS_SERVER_PORT:-4444}" export TRAPOS_SERVER_PORT="${TRAPOS_SERVER_PORT:-4444}"

View File

@ -3,11 +3,14 @@ import { createApp, type Config } from "./app.js";
const config: Config = { const config: Config = {
host: process.env.TRAPOS_SERVER_HOST ?? "0.0.0.0", host: process.env.TRAPOS_SERVER_HOST ?? "0.0.0.0",
port: readPort(process.env.TRAPOS_SERVER_PORT, 4444), port: readPort(process.env.TRAPOS_SERVER_PORT, 4444),
serverPassword: process.env.TRAPOS_SERVER_PASSWORD, serverPassword: requireSecret("TRAPOS_SERVER_PASSWORD", process.env.TRAPOS_SERVER_PASSWORD),
requestTimeoutMs: readPositiveInt(process.env.TRAPOS_SERVER_REQUEST_TIMEOUT_MS, 600_000), requestTimeoutMs: readPositiveInt(process.env.TRAPOS_SERVER_REQUEST_TIMEOUT_MS, 600_000),
opencodeUrl: process.env.OPENCODE_URL ?? "http://127.0.0.1:4242", opencodeUrl: process.env.OPENCODE_URL ?? "http://127.0.0.1:4242",
opencodeUsername: process.env.OPENCODE_USERNAME ?? "opencode", opencodeUsername: process.env.OPENCODE_USERNAME ?? "opencode",
opencodeServerPassword: process.env.OPENCODE_SERVER_PASSWORD, opencodeServerPassword: requireSecret(
"OPENCODE_SERVER_PASSWORD",
process.env.OPENCODE_SERVER_PASSWORD,
),
// MCP endpoint: on by default, bound to localhost. exec-lua / write-file are as // MCP endpoint: on by default, bound to localhost. exec-lua / write-file are as
// powerful as the linked computer, so the listener stays off the public gateway port. // powerful as the linked computer, so the listener stays off the public gateway port.
mcpEnabled: process.env.MCP_ENABLED !== "0", mcpEnabled: process.env.MCP_ENABLED !== "0",
@ -53,6 +56,22 @@ function buildLogger(): Config["logger"] {
return { level }; return { level };
} }
// A non-empty password is mandatory: an empty TRAPOS_SERVER_PASSWORD disables auth
// on the public gateway (resolveAccount maps any secret to "local"), and an empty
// OPENCODE_SERVER_PASSWORD drops the outbound Basic-auth header. Refuse to start
// rather than run silently unauthenticated.
function requireSecret(name: string, value: string | undefined): string {
const secret = value?.trim();
if (!secret) {
process.stderr.write(
`\n[trapos-server] ${name} is empty or unset. Set it in .env ` +
`(run \`just install\` to generate secrets). Refusing to start.\n\n`,
);
process.exit(1);
}
return secret;
}
function readPort(value: string | undefined, fallback: number): number { function readPort(value: string | undefined, fallback: number): number {
return readPositiveInt(value, fallback); return readPositiveInt(value, fallback);
} }