Network Mode and Security
By default the API is reachable only over the local Unix socket (mode 0600) or Windows named pipe, which are same-user only and fully trusted: no authentication, all scopes, no path restrictions. An optional TCP (and WebSocket) transport can be enabled for off-box or cross-process clients. That transport is authenticated, scope-checked and path-jailed on every request; the local socket is unaffected.
Enabling the network transport
Set environment variables before starting the launcher. The variables are currently named PANDAPI_API_*; PANDAPI_* aliases are planned as part of the Pandapi rebrand (with the PANDAPI_API_* names staying authoritative), but today only these work:
| Variable | Effect |
|---|---|
PANDAPI_API_TCP |
Bind a TCP listener. Value is host:port, or a bare port (interpreted as 127.0.0.1:<port>). Unset = no network transport. |
PANDAPI_API_WS |
1/true to also accept WebSocket clients on the same port (the listener sniffs an HTTP GET upgrade; raw NDJSON still works on the same port). |
PANDAPI_API_NO_AUTH |
1/true to disable authentication. Honored only for a loopback bind; a non-loopback bind forces auth back on and logs a warning. |
PANDAPI_API_PATH_ALLOW |
Colon-separated directories that network clients may reference in path parameters. Unset or empty = every path parameter denied. |
Example (headless host):
PANDAPI_API_TCP=127.0.0.1:8730 PANDAPI_API_WS=1 \
PANDAPI_API_PATH_ALLOW="$HOME/mc-share" \
ekaii-launcher --headless
net.status reports the live configuration (bind, websocket, require_auth, allowlist, active connections, token count).
Minting tokens
auth.mint_token is local-socket only: tokens are minted on the launcher host, never over the network, so the network can never bootstrap its own credentials.
# On the launcher host:
ekaii-launcher --api auth.mint_token \
--api-params '{"scopes":["read","write"],"ttl_secs":86400,"label":"ci"}'
# {"token":"Zm9vYmE.c2VjcmV0...","token_id":"Zm9vYmE","scopes":["read","write"],
# "expires_unix":1765486400,"warning":"shown once; store it now, it is not recoverable"}
- The full token (
<token_id>.<secret>) is shown once; only a hash is persisted (api_tokens.json, mode0600). ttl_secsis optional; omit for a non-expiring token.labelis free text for auditing.- Manage with
auth.list_tokensandauth.revoke_token(both local-only). Revocation and TTL expiry take effect on the next request of any connection already using the token, not just on reconnect.
Authenticating a network connection
Pre-auth, only these commands are allowed: auth.hello, auth.bearer, auth.prove, auth.whoami, launcher.version. A typical session over nc 127.0.0.1 8730 or a WebSocket:
{"id":1,"cmd":"auth.hello"}
{"id":1,"ok":true,"data":{"auth_required":true,"methods":["bearer","hmac"],"nonce":"9f86d081...","transport":"tcp","session_id":"18f3c0a1b2"}}
{"id":2,"cmd":"auth.bearer","params":{"token":"Zm9vYmE.c2VjcmV0..."}}
{"id":2,"ok":true,"data":{"authenticated":true,"token_id":"Zm9vYmE","scopes":["read","write"],"label":"ci"}}
{"id":3,"cmd":"instances.list"}
Two methods:
auth.bearer: send the full token string. Simple; the secret crosses the (tunneled) wire.auth.prove: sendtoken_idplus the hexHMAC-SHA256(secret, nonce)over thenoncefromauth.hello, so the secret never crosses the wire. Works only for tokens minted in the current launcher session (hmac_capableinauth.list_tokens); after a restart, fall back toauth.bearer.
auth.whoami reports the connection's transport, identity and scopes at any time.
Scopes
| Scope | Covers |
|---|---|
read |
Observe-only commands: listings, logs, metrics, state.export, status.summary, api.describe, events.subscribe, ops.list/status/wait, net.status, settings.get, sync.state, accounts.list, ... |
write |
Everything not in the read allowlist (fail-safe default for new commands). A write token also satisfies read. |
accounts |
Account mutations (accounts.* except accounts.list) |
settings |
settings.* and sync.* writes |
* |
Everything |
Mint the narrowest token that does the job: read for dashboards and observers, write without accounts/settings for most automation. The machine-readable version of this whole model (scopes, the exact read-only set, the local-only set, denied fields, env vars, error codes) is in api.describe under security.
Local-socket-only commands
Refused over TCP/WebSocket regardless of scope, with forbidden:
auth.mint_token,auth.list_tokens,auth.revoke_token(credential bootstrap)launcher.quit,launcher.install_update(host control)hooks.register,hooks.delete(a persistent outbound channel: SSRF + event exfiltration;hooks.listremains readable)servers.status(connects to arbitrary caller-chosen addresses: an SSRF / port-scan primitive)
The path allowlist and denied fields
Every path parameter from a network client must canonicalize under an PANDAPI_API_PATH_ALLOW root or the request fails with path_denied. Covered: logs.read/logs.tail/diagnostics.analyze/logs.upload path, instances.export output, instances.import_file file, instances.relocate/instances.create_shortcut path, content.dev_link source_glob, skins.add file, worlds.restore backup, import.scan path, import.run root and paths[], the nested file.path entries of content.install/content.resolve/content.sync, and icon.path.
Independently of the allowlist, the execution-vector fields wrapper_command, linux_wrapper, system_libraries, jvm_binary and jvm_flags are refused outright over the network on any command, including through instances.ensure. A network client cannot make the launcher execute arbitrary programs.
No TLS: loopback and tunnels
The network transport is plaintext. It is intended for loopback or an already-encrypted tunnel; do not expose it directly to an untrusted network. Recommended patterns:
# SSH tunnel from your workstation to a launcher host bound to 127.0.0.1:8730
ssh -N -L 8730:127.0.0.1:8730 user@gamebox
# then connect to 127.0.0.1:8730 locally and auth.bearer as usual
- WireGuard / VPN: bind to the VPN interface address and let the tunnel provide the encryption.
- Reverse proxy: with
PANDAPI_API_WS=1, put the WebSocket endpoint behind an HTTPS-terminating reverse proxy; keep the launcher bind on loopback. - Even inside a tunnel, keep auth on;
PANDAPI_API_NO_AUTHis for loopback development only.
WebSocket details
With PANDAPI_API_WS=1, clients connect with a standard RFC6455 handshake (the implementation is built in, no external web stack). The protocol on top is unchanged: one JSON message per frame (one request per client frame; each response/event line is one server text frame). Fragmented messages are reassembled, ping/pong frames are ignored, client frames must be masked, and the message cap is 1 MiB.
Error codes
Authorization failures use the structured error object (see Concepts): unauthorized (authenticate first, or the token is unknown/revoked/expired/bad), forbidden (missing scope, or a local-socket-only command over the network), path_denied (path outside the allowlist, or an execution-vector field set). Branch on the code; message carries the specific reason.